mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-24 15:01:38 +00:00
update rules
This commit is contained in:
parent
4c390fdd37
commit
d5522b2fef
8549 changed files with 11783 additions and 1888 deletions
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: Get started
|
||||
description: Explains how to get started with opening a project.
|
||||
next: /tutorials/curriculums/animator/work-with-the-animation-editor
|
||||
prev: /tutorials/curriculums/animator/
|
||||
---
|
||||
|
||||
Welcome to your first beginner animation lesson in Studio! To get started, let's walk through how to open the sample template, customize Studio's layout for your tasks, and playtest the core gameplay of the experience where players move around a track to earn stars.
|
||||
|
||||
## Open template
|
||||
|
||||
Templates are pre-made 3D worlds that you can use as the base for your own experiences. To introduce you to animation in Studio, Roblox has created the **Island of Move** template with everything necessary for the experience to work properly, such as the code to run the gameplay and an environment that you can customize at a later time.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/opening-the-template/template-example.png" width="70%" />
|
||||
|
||||
<Alert severity="info">
|
||||
For a full list of Roblox-provided templates, see [Templates](../../../resources/templates.md).
|
||||
</Alert>
|
||||
|
||||
Click the following button to open the **Island of Move** template:
|
||||
|
||||
<UseStudioButton variant="" buttonTextTranslationKey="Action.EditInStudio" placeId="5353920686" universeId="1876281622" />
|
||||
|
||||
## Customize workspace
|
||||
|
||||
Roblox Studio launches with extra windows open that you don't need for this lesson. You can close these extra windows to create more space for what you're currently working on.
|
||||
|
||||
To customize the workspace for your animating task:
|
||||
|
||||
1. Close any open window on the **left side** of Studio by clicking the **×** in the window's upper-right corner. If you don't see anything to close, go to the next step.
|
||||
|
||||
2. Leave the **Explorer** and **Properties** window open on the right side of Studio. If you have done each step correctly, Studio looks like the following image.
|
||||
|
||||
<img src="../../../assets/education/general/Studio-Setup-Island-Of-Move.jpg" width="960" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, open them from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
## Playtest the experience
|
||||
|
||||
Before you start animating, it's important to take a moment to playtest the template so you can see how the gameplay works. When players join the experience, they make progress around the track by clicking the move button, or using a mobile device and moving around in real-life.
|
||||
|
||||
To playtest the experience's gameplay:
|
||||
|
||||
1. In the top-left corner of Studio, keep the default **Test** playtest option, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
<Alert severity="info">
|
||||
There are a variety of additional playtest options that you will explore in a later chapter of the tutorial.
|
||||
</Alert>
|
||||
|
||||
1. In the experience, use the following player controls to walk up to the golem character on the stage, then press <kbd>E</kbd> to open up a menu. From that menu, click **Play Game**.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> or arrow keys</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button and look around.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Pan**</td>
|
||||
<td>Hold the middle mouse button to drag your camera around.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/test-the-game/click-npc.png" width="50%" />
|
||||
|
||||
1. When the experience starts, go forward by repeatedly clicking the **Move** button. Your character automatically follows the track as you click.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/test-the-game/click-game-button.mp4" width="80%"></video>
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
Now that you are familiar with the gameplay, it's time to start animating!
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: Intro to animation
|
||||
description: Learn the basics of animating characters.
|
||||
next: /tutorials/curriculums/animator/get-started
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<img src="../../../assets/tutorials/landing/AnimationThumbnail.png" width="90%" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
Using the [Island of Move](https://www.roblox.com/games/5306359293/Island-of-Move) sample as a guide, this tutorial acts as an introduction to creating animations in Roblox Studio. As you follow each chapter, you will learn how to create keyframes for various poses, then loop them into a swimming animation that you can reuse in any experience.
|
||||
|
||||
This lesson is intended for readers who are **brand new** to creating animations and working in Studio. If you are familiar with both, try another beginner lesson plan or jump straight to [building your first experience](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<Alert severity = 'info'>
|
||||
If you don't have a Roblox account, Studio active on your device, or familiarity with Studio's UI, see the [Studio lesson](../studio/index.md).
|
||||
</Alert>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Get started</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [open and customize](get-started.md) the template's workspace so that you can work with a poseable figure.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Work with the Animation Editor</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [use the Animation Editor](work-with-the-animation-editor.md) to create keyframes for different poses of an animation.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Play your animation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [export and replace](play-your-animation.md) the character's default run animation with your new animation.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Test and save</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [playtest your animation](test-and-save.md) and publish your work to Roblox.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 5 - Next steps</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn where you can [find more tutorials](next-steps.md) about animating characters for experiences.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: Next steps
|
||||
description: Explains ways to continue learning about creating animations in Studio.
|
||||
prev: /tutorials/curriculums/animator/test-and-save
|
||||
---
|
||||
|
||||
Congratulations on completing your first animation with Roblox Studio! Now that you have experience working with keyframes and posing characters, you can share your work with others and extend your skills by learning about Studio's additional animation features.
|
||||
|
||||
## Share with others
|
||||
|
||||
When you first publish an experience, it's automatically set to **private** so that you are the only one who can play it. However, creating on Roblox is always more fun when you can share what you make with others, so let's set up your experience so that they can play too.
|
||||
|
||||
To make your experience public:
|
||||
|
||||
1. Using the steps from the last chapter, publish your experience so others can see your latest changes.
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Experience Settings**.
|
||||
|
||||
1. In the settings window:
|
||||
1. Go to the left bar and select the **Permissions** tab. New settings display.
|
||||
1. Set **Playability** to **Public**.
|
||||
|
||||
1. Click the **Save** button. Now everyone on Roblox can play your experience!
|
||||
|
||||
## Continue learning
|
||||
|
||||
The basic **Animation Editor** tools are an excellent start while learning to create animations, but Studio offers many additional animation features to add liveliness to your characters and environment, including:
|
||||
|
||||
- [Event markers](../../../animation/events.md) that trigger custom behavior at specific keyframes in an animation.
|
||||
- [Inverse Kinematics](../../../animation/inverse-kinematics.md) that let you pose and animate multiple character parts by posing or adjusting a single object.
|
||||
|
||||
- The [Curve Editor](../../../animation/curve-editor.md) interface that lets you see and modify how a rig's position and orientation changes between keyframes through color-coded curve graphs.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<figure>
|
||||
<img src="../../../assets/animation/animation-editor/Animation-Events-Add-Event.png" alt="A single event marker called FootStep with an empty parameter for an event." width="800" />
|
||||
<figcaption>Event markers</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/animation/curve-editor/Curve-Editor-Example.png" alt="A preview of four curves on the Curve Editor interface." width="61%" />
|
||||
<figcaption>Curve Editor interface</figcaption>
|
||||
</figure>
|
||||
</figure>
|
||||
<figure>
|
||||
<video src="../../../assets/animation/inverse-kinematics/IK-Place-Hand.mp4" controls width="75%" style={{marginTop: 0}}></video>
|
||||
<figcaption>Inverse Kinematics</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
If you want to continue learning through step-by-step lessons, check out the following use case tutorials:
|
||||
|
||||
- [Create character animations](../../use-case-tutorials/animation/create-an-animation.md)
|
||||
- [Play character animations](../../use-case-tutorials/animation/play-character-animations.md)
|
||||
- [Rig simple meshes in Blender](../../../art/modeling/rig-a-simple-mesh.md)
|
||||
- [Skin simple meshes in Blender](../../../art/modeling/skin-a-simple-mesh.md)
|
||||
- [Rig humanoid models in Blender](../../../art/modeling/rig-a-humanoid-model.md)
|
||||
- [Skin humanoid models in Blender](../../../art/modeling/skin-a-humanoid-model.md)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
title: Play your animation
|
||||
description: Explains how to export and replace a default character animation with your custom animation.
|
||||
next: /tutorials/curriculums/animator/test-and-save
|
||||
prev: /tutorials/curriculums/animator/work-with-the-animation-editor
|
||||
---
|
||||
|
||||
In order to play your animation in your open experience, as well as store it for reuse in other projects, you must publish the animation to the cloud. This process creates a unique animationID for your animation that you can reference in scripts, which is especially important if you want to replace any of Roblox's default character animations.
|
||||
|
||||
In this chapter, let's go over how to export your animation by publishing it to Roblox, then swap out the template's default walk animation with your own creation.
|
||||
|
||||
## Export new animation
|
||||
|
||||
If you were to close Studio right now, you would lose all of your work on your animation because your animation doesn't have an **animationID** that Studio can reference for your projects. To fix this, you must export your animation to Roblox so that Studio can connect the animation to your account.
|
||||
|
||||
To export your new animation:
|
||||
|
||||
1. In the **Animation Editor**, click on the **⋯** and select **Publish to Roblox**. The **Asset Configuration** window displays.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/publish-animations/Publish-Highlight.png" width="60%" />
|
||||
|
||||
1. In the **Asset Configuration** window, give your animation a name and description, then click the **Save** button. After a few moments, the window confirms your animation was successfully submitted.
|
||||
|
||||
1. Copy your animation's animationID by clicking the icon next to the ID. A green "_ID Copied!_" text pop-up displays when you have done this step correctly.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/publish-animations/publish-copy-id.png" width="50%" />
|
||||
|
||||
1. Click the **Close** button.
|
||||
|
||||
## Replace default animation
|
||||
|
||||
Every player character includes a set of **default animations** that play whenever the character performs specific in-experience actions, such as running, climbing, or jumping. Roblox provides these animations out-of-the-box for every experience without any additional coding effort.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<video controls src="../../../assets/tutorials/playing-character-animations/Default-Fall-Animation.mp4" width="100%"></video>
|
||||
<figcaption>Default Fall Animation</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<video controls src="../../../assets/tutorials/playing-character-animations/Default-Swim-Animation.mp4" width="100%"></video>
|
||||
<figcaption>Default Swim Animation</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<video controls src="../../../assets/tutorials/playing-character-animations/Default-Climb-Animation.mp4" width="100%"></video>
|
||||
<figcaption>Default Climb Animation</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
You can swap out default animations with your own custom animations so that they play for every player that joins your experience. For this lesson, let's swap out the default walking animation with your swim animation so that you can see how it looks with your avatar as soon as you start playing.
|
||||
|
||||
To replace the default walking animation with your custom swimming animation:
|
||||
|
||||
1. Find the default walk animation.
|
||||
1. In the **Explorer** window, go to the search bar, then type **walk**.
|
||||
1. In your search results, click on **WalkAnimationID**.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/change-the-animation/search-walk-select.png" width="50%" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, open them from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
1. In the **Properties** window, click the **Value** field and paste your animation ID by pressing <kbd>Ctrl</kbd><kbd>V</kbd> or <kbd>⌘</kbd><kbd>V</kbd>, depending on your computer.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/change-the-animation/set-animation-value.png" width="50%" />
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Need to Get the Animation ID?</AlertTitle>
|
||||
If your animationID didn't paste into the Value field, you need to get your animation's animationID again. To do this:
|
||||
|
||||
1. Go to the [Creator Dashboard](https://create.roblox.com/dashboard/creations).
|
||||
1. In the horizontal navigation, select **Development Items**, then click the **Animations** button. All animations you have ever published display here.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/change-the-animation/Animations-Button.png" width="100%" />
|
||||
|
||||
1. Select your animation. The animation's **Configure** page displays.
|
||||
1. In the left-hand navigation, click the vertical three dots icon, then select **Copy Asset ID** from the contextual menu.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/change-the-animation/CopyAssetID.png" width="50%" />
|
||||
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
---
|
||||
title: Test and save
|
||||
description: Explains how to playtest and save your fitness experience.
|
||||
next: /tutorials/curriculums/animator/next-steps
|
||||
prev: /tutorials/curriculums/animator/play-your-animation
|
||||
---
|
||||
|
||||
Now that your poses are complete and set up to play as your character's run animation, it's time to playtest your experience to verify that the animation plays properly, then publish your work to Roblox so that you can save your progress.
|
||||
|
||||
## Playtest your animation
|
||||
|
||||
It's important to playtest your experience often to ensure that your animation plays exactly the way you want it to play for your players. Studio offers several playtest options:
|
||||
|
||||
- **Test** – Starts the playtest by inserting your avatar at a spawn location.
|
||||
- **Test Here** – Starts the playtest by inserting your avatar in front of the camera's current position.
|
||||
- **Run** – Starts the playtest without inserting your avatar. Instead, the playtest begins at the current camera position and you can navigate around using the camera controls.
|
||||
|
||||
While all options are useful at different times, it's important to use a playtest option that inserts your avatar so that you can see your animation play.
|
||||
|
||||
To playtest your animation:
|
||||
|
||||
1. In the top-left corner of Studio, verify that your playtest is set to either **Test** or **Test Here**, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
1. Use the <kbd>WASD</kbd> keys to walk around and see the animation play.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/change-the-animation/finished-animation.mp4" width="70%"></video>
|
||||
|
||||
<Alert severity="warning">
|
||||
If you make updates to the animation, remember to **Save** it in the **Animation Editor**.
|
||||
</Alert>
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
If you're running into any issues with your animation, check out the following troubleshooting tips:
|
||||
|
||||
**Issue:** Animation is spotty, skips, or looks glitchy.
|
||||
|
||||
- Make sure that the first and last poses are the same. If they aren't, right-click on the diamond for the first pose and copy it, then paste that pose at the end of the timeline.
|
||||
- Check that the figure stays inside the white grid when you select it. If not, it may go through walls or the ground when the animation plays.
|
||||
|
||||
**Issue:** Animation isn't the one that you were working on.
|
||||
|
||||
- Check that you've published the animation through the **Animation Editor**.
|
||||
- In the **Explorer** window, find an object named **WalkAnimationID**, then make sure its `Value` property is only a number.
|
||||
|
||||
## Publish to Roblox
|
||||
|
||||
You have access to your animation ID for other projects, but if you were to close Studio now, you would lose every edit you made to your experience. For this reason, it's important to publish your work to Roblox often to save your work and connect the experience to your account.
|
||||
|
||||
<Alert severity="info">
|
||||
It's recommended to publish to Roblox every ten minutes or after making a big change.
|
||||
</Alert>
|
||||
|
||||
1. In the top-left corner of your computer, click **File** → **Publish to Roblox** to open the publishing window.
|
||||
|
||||
1. In the **Publish Game** window,
|
||||
|
||||
1. In the **Name** field, provide a name for your experience.
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> In the **Description** field, provide a summary of what a player can expect from the experience.
|
||||
1. In the **Devices** section, enable every device you want players to use to access your experience. Because this experience allows players to use mobile devices with accelerometers as a way to move characters, it's recommended to at least enable phones.
|
||||
1. At the bottom-right of the window, click the **Create** button.
|
||||
|
||||
Now that your experience is published and connected to your account, you can edit it from any computer!
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Saving after publishing</AlertTitle>
|
||||
Next time you want to save your work, just go to **File** → **Publish to Roblox** or use the hotkey combo (<kbd>Alt</kbd><kbd>P</kbd>/<kbd>⌥</kbd><kbd>P</kbd>).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
---
|
||||
title: Work with the Animation Editor
|
||||
description: Explains how to work with the Animation Editor to create a custom animation.
|
||||
next: /tutorials/curriculums/animator/play-your-animation
|
||||
prev: /tutorials/curriculums/animator/get-started
|
||||
---
|
||||
|
||||
The **Animation Editor** lets you move and rotate your characters' body parts like an action figure into unique poses at different keyframes of an animation. Studio then automatically creates keyframes in between those poses that flow together to make your characters appear as if they're moving.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/creating-animations/AnimationExample.mp4" width="60%"></video>
|
||||
|
||||
In this chapter, let's explore how to open the **Animation Editor**, customize your settings to help with posing your character, and create an animation with two unique poses that Studio flows together to make it look like the character is swimming in water.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/creating-animations/show-animation-breakdown.mp4" width="60%"></video>
|
||||
|
||||
## Open Animation Editor
|
||||
|
||||
The **Animation Editor** is Studio's primary animation tool. While it comes with a lot of functionality, such as [animation capture](../../../animation/capture.md) and [inverse kinematics](../../../animation/inverse-kinematics.md) tools, this lesson focuses on its basic tools to help you get started in learning how to create animations.
|
||||
|
||||
1. At the top of the toolbar, go to the **Avatar** tab.
|
||||
1. Click the **Animation** button. The **Animation Editor** displays at the bottom of Studio.
|
||||
|
||||
## Prepare rig
|
||||
|
||||
The **Animation Editor** requires a **rig**, or poseable figure, to create character animations because rigs include the internal structure you need to move and rotate body parts into different poses. While you can create your own rigs or access several pre-built rigs through the **Rig Generator** tool, this lesson provides a rig on top of the pedestal in the experience.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/creating-animations/highlight-figure.jpg" width="100%" />
|
||||
|
||||
To prepare your rig:
|
||||
|
||||
1. Select the **Figure** rig on top of the pedestal. The part names that make up the rig's body parts display in the **Animation Editor**.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/creating-animations/rig-selected.jpg" width="80%" />
|
||||
|
||||
1. In the toolbar, find the snapping tools and make sure **Rotate** and **Move** are not checked.
|
||||
|
||||
<img src="../../../assets/education/general/Snap-Settings-C.png" width="510" />
|
||||
|
||||
1. Using the camera controls, move the camera to get a good view of the part you want to animate. While moving, hold <kbd>Shift</kbd> to slow down camera movements.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button to rotate around the selected object.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Zoom**</td>
|
||||
<td>Use your mouse's scroll wheel.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Focus**</td>
|
||||
<td>Press <kbd>F</kbd> to focus on a the selected object.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/creating-animations/show-move-camera.mp4" width="80%"></video>
|
||||
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Can't move the camera?</AlertTitle>
|
||||
If the camera doesn't move when you press <kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd>, click somewhere inside the 3D viewport.
|
||||
</Alert>
|
||||
|
||||
## Create first pose
|
||||
|
||||
Now that the **Animation Editor** is open and your rig preparation is complete, it's time to start posing your character for the first pose of your animation. This lesson focuses on a swimming animation, but feel free to adjust the learnings for your own poses. Depending on the animation you want to create, your first pose may be very different.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/example-swim.jpeg" />
|
||||
<figcaption>Swimming</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/example-crouch.jpeg" />
|
||||
<figcaption>Crouching</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/example-dance.jpeg" />
|
||||
<figcaption>Dancing</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="info">
|
||||
Creating poses is an art form, and it takes time to figure out how to move and rotate body parts to meet the needs of your animation. It's normal and expected for your poses to look and feel different from the lesson's video examples.
|
||||
</Alert>
|
||||
|
||||
To create your first pose for a swimming animation:
|
||||
|
||||
1. Back in the toolbar, select the **Rotate** tool, then click on the rig's **LeftUpperArm**. A set of handles display around the body part.
|
||||
|
||||
<img src="../../../assets/education/general/Rotate-Tool.png" width="328" />
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/click-upper-arm.mp4" width="80%"></video>
|
||||
|
||||
1. Click and drag the handles to rotate the rig's left arm, and note how each handle rotates the arm in a different direction. You can undo any change by pressing either <kbd>Ctrl</kbd><kbd>Z</kbd> or <kbd>⌘</kbd><kbd>Z</kbd>, depending on your computer.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/rotate-upper-arm.mp4" width="80%"></video>
|
||||
|
||||
1. Rotate different body parts until your first pose is complete. Make sure to change the camera angle as you're working so that you can see the pose from multiple points of view.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/create-the-first-pose/timelapse-first-pose.mp4" width="80%"></video>
|
||||
|
||||
<Alert severity="warning">
|
||||
If you're running into any issues while trying to rotate your rig's body parts, check out the following troubleshooting tips:
|
||||
|
||||
**Issue:** You can't click on rotation handles.
|
||||
|
||||
- Another part may be blocking your click.
|
||||
- Try rotating around your camera until you see a blue box whenever you hover over the part you want to click.
|
||||
|
||||
**Issue:** You can't see the rotation handles.
|
||||
|
||||
- If you see a set of arrows instead of the circles, you need to select the **Rotate** tool in the toolbar.
|
||||
</Alert>
|
||||
|
||||
## Duplicate keyframes
|
||||
|
||||
Before you begin your second pose, let's take a moment to duplicate your first pose at the end of your animation. This process ensures that your animation repeats smoothly as it loops.
|
||||
|
||||
To move your first pose to the end of the animation, you need to move the **scrubber** that marks the current keyframe position to the end of the timeline. The **timeline** is the numbered line at the top of the **Animation Editor** that represents your animation in seconds.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/looping-animations/Scrubber.png" />
|
||||
<figcaption>Scrubber</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/looping-animations/Timeline.png" />
|
||||
<figcaption>Timeline</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To duplicate your first pose's keyframes to the end of the animation:
|
||||
|
||||
1. In the **Animation Editor**, click on the **white diamond** at the top of the timeline. This diamond represents every body part's rotational data for your first pose at `0:00` on the timeline. Note that the white diamond displays with a blue border when you have done this step correctly.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/looping-animations/select-first-pose-diamond.png" width="80%" />
|
||||
|
||||
1. Copy the first pose by pressing <kbd>Ctrl</kbd><kbd>C</kbd> or <kbd>⌘</kbd><kbd>C</kbd>, depending on your computer.
|
||||
1. Click the **scrubber**, then drag it to the end of the animation at `1:00` on the timeline.
|
||||
1. Paste your first pose at this new timeline position by pressing <kbd>Ctrl</kbd><kbd>V</kbd> or <kbd>⌘</kbd><kbd>V</kbd>, depending on your computer. A new set of diamonds display at the end keyframe.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/looping-animations/added-last-pose.png" width="80%" />
|
||||
|
||||
1. To loop the animation, click the **Loop** icon. Your animation will now repeat seamlessly.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/adding-the-second-pose/press-loop-button.png" width="80%" />
|
||||
|
||||
## Create second pose
|
||||
|
||||
Using the same process as earlier in the lesson, it's time to create your second pose! After you design this unique pose, Studio will automatically create keyframes in between those poses that flow together to make your characters appear as if they're swimming.
|
||||
|
||||
To create your second pose for a swimming animation:
|
||||
|
||||
1. Set the scrubber in the middle of the animation. You can either drag the scrubber to roughly be in the middle of the timeline, or you can manually set it to `0.5` in the **position indicator** to the left of the timeline.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-island-of-move/adding-the-second-pose/set-timeline-middle.png" width="60%" />
|
||||
|
||||
1. With the **Rotate** tool still active, rotate different body parts until your second pose is complete. Remember to change the camera angle as you're working so that you can see the pose from multiple points of view.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-island-of-move/adding-the-second-pose/show-middle-timelapse.mp4" width="80%"></video>
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Posing Faster</AlertTitle>
|
||||
Speed up posing by rotating body parts connected to the torso first, such as the upper arm and thigh, then work outwards to pose the hands and feet.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Start a Pose from Scratch</AlertTitle>
|
||||
Don't like a pose and want to just start from the beginning? You can reset your character to the default pose using the following process:
|
||||
|
||||
1. Right click on the top white diamond of the pose you want to reset. A menu displays.
|
||||
|
||||
1. From the menu, select **Reset Selected**.
|
||||
</Alert>
|
||||
|
||||
1. In the **Animation Editor**, click the **Play** button to test your animation. Feel free to make adjustments to either of your poses until it looks like the character is smoothly swimming in water.
|
||||
|
||||
Congratulations on posing your character for your first animation! The next lesson details how to save and export your animation so that you can use it within experiences.
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
title: Get started
|
||||
description: Explains how to get started with opening a project.
|
||||
next: /tutorials/curriculums/artist/work-with-particle-emitters
|
||||
prev: /tutorials/curriculums/artist/
|
||||
---
|
||||
|
||||
Welcome to your first beginner VFX art lesson in Studio! To get started, let's walk through how to open the sample template, customize Studio's layout for your tasks, and playtest the core gameplay of the experience where players join a carnival ride and defend their cart from enemies.
|
||||
|
||||
## Open template
|
||||
|
||||
Templates are pre-made 3D worlds that you can use as the base for your own experiences. To introduce you to animation in Studio, Roblox has created the **Mansion of Wonder** template with everything necessary for the experience to work properly, such as the code to run the gameplay and an environment that you can customize at a later time.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/become-an-artist/template-overview.png" width="70%" />
|
||||
|
||||
<Alert severity="info">
|
||||
For a full list of Roblox-provided templates, see [Templates](../../../resources/templates.md).
|
||||
</Alert>
|
||||
|
||||
Click the following button to open the **Mansion of Wonder** template:
|
||||
|
||||
<UseStudioButton variant="" buttonTextTranslationKey="Action.EditInStudio" placeId="6936227200" universeId="2653926164" />
|
||||
|
||||
## Customize workspace
|
||||
|
||||
Roblox Studio launches with extra windows open that you don't need for this lesson. You can close these extra windows to create more space for what you're currently working on.
|
||||
|
||||
To customize the workspace for your VFX art task:
|
||||
|
||||
1. Close each window on the **left** of the 3D view by clicking the **×** in the window's upper-right corner. If you don't see anything to close on the left, go to the next step.
|
||||
|
||||
2. Leave the **Explorer** and **Properties** windows open on the right side of Studio. If you have done each step correctly, Studio looks like the following image.
|
||||
|
||||
<img src="../../../assets/education/general/Studio-Setup-Mansion-Of-Wonder.jpg" width="960" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, toggle them on from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
## Playtest the experience
|
||||
|
||||
Before you start creating VFX art, it's important to take a moment to playtest the template so you can see how the gameplay works. When players join the experience and jump into the carnival ride, slimes and ghosts begin to slowly advance toward the player's moving cart. If these enemies touch the cart too many times, the cart explodes.
|
||||
|
||||
To playtest the experience's gameplay:
|
||||
|
||||
1. In the top-left corner of Studio, keep the default **Test** playtest option, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
<Alert severity="info">
|
||||
There are a variety of additional playtest options that you will explore in a later chapter of the tutorial.
|
||||
</Alert>
|
||||
|
||||
1. In the experience, use the following player controls to walk up to the golem character on the stage, then press <kbd>E</kbd> to open up a menu. From that menu, click **Play Game**.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> or arrow keys</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button and look around.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Pan**</td>
|
||||
<td>Hold the middle mouse button to drag your camera around.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/test-and-play/start-game.png" width="50%" />
|
||||
|
||||
1. When the ride starts, left click to shoot the blaster at enemies. Watch the cart's health at the top of the screen and try and make it to the treasure room at the end.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/test-and-play/game-health.png" width="80%" />
|
||||
|
||||
<Alert severity="warning">
|
||||
During the ride, you can't use your mouse to click parts of Studio. To use the mouse, press <kbd>1</kbd> on the keyboard.
|
||||
</Alert>
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
Now that you are familiar with the gameplay, it's time to start creating special effects!
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Intro to VFX art
|
||||
description: Learn the basics of creating special effects for experiences.
|
||||
next: /tutorials/curriculums/artist/get-started
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<img src="../../../assets/tutorials/landing/VFXArtThumbnail.png" width="90%" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
Using the [Mansion of Wonder](https://www.roblox.com/games/6901029464/Mansion-of-Wonder) sample as a guide, this tutorial acts as an introduction to creating special effects (VFX) in Roblox Studio using particle emitters. As you follow each chapter, you will learn how to create a laser blast and explosion that players can use as they defeat sinister slimes and ghastly ghosts in a spooky carnival experience.
|
||||
|
||||
This lesson is intended for readers who are **brand new** to creating VFX art and working in Studio. If you are familiar with both, try another beginner lesson plan or jump straight to [building your first experience](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<Alert severity = 'info'>
|
||||
If you don't have a Roblox account, Studio active on your device, or familiarity with Studio's UI, see the [Studio lesson](../studio/index.md).
|
||||
</Alert>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Get started</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [open and customize](get-started.md) the template's workspace so that you can focus on creating colorful VFX.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Work with particle emitters</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [change the appearance and behavior](work-with-particle-emitters.md) of particles in the carnival ride.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Test and save</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [playtest your art](test-and-save.md) and publish your work to Roblox.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Next steps</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn where you can [find more tutorials](next-steps.md) about VFX for experiences.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: Next steps
|
||||
description: Explains ways to continue learning about creating special effects in Studio.
|
||||
next: /tutorials/curriculums/animator/
|
||||
prev: /tutorials/curriculums/artist/test-and-save
|
||||
---
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/invite-friends/hero-image.jpeg" />
|
||||
|
||||
Congratulations on completing your first VFX art with Roblox Studio! Now that you have experience creating custom special effects with unique visuals and behavior, you can share your art with others and extend your skills by learning about Studio's additional VFX features.
|
||||
|
||||
## Share with others
|
||||
|
||||
When you first publish an experience, it's automatically set to **private** so that you are the only one who can play it. However, creating on Roblox is always more fun when you can share what you make with others, so let's set up your experience so that they can play too.
|
||||
|
||||
To make your experience public:
|
||||
|
||||
1. Using the steps from the last chapter, publish your experience so others can see your latest changes.
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Experience Settings**.
|
||||
|
||||
1. In the settings window:
|
||||
1. Go to the left bar and select the **Permissions** tab. New settings display.
|
||||
1. Set **Playability** to **Public**.
|
||||
|
||||
1. Click the **Save** button. Now everyone on Roblox can play your experience!
|
||||
|
||||
## Continue learning
|
||||
|
||||
Particle emitters are an excellent start for creating VFX, but Studio offers many additional VFX features to boost the visuals of your experiences, including:
|
||||
|
||||
- [Beams](../../../effects/beams.md) that render a texture between two points that stay still.
|
||||
- [Trails](../../../effects/trails.md) that render a texture between two points that move.
|
||||
- [Flipbooks](../../../effects/particle-emitters.md#flipbooks) that animate a texture over time.
|
||||
- [Light sources](../../../effects/light-sources.md) that emit light at specific points in the 3D space.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<video src="../../../assets/lighting-and-effects/beam/Showcase.mp4" controls
|
||||
width="100%"></video>
|
||||
<figcaption>Beams</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<video src="../../../assets/lighting-and-effects/trail/Showcase.mp4" controls width="100%"></video>
|
||||
<figcaption>Trails</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<video src="../../../assets/lighting-and-effects/particle-emitter/Flipbook.mp4" controls width="800" alt="Flipbook looping over the four particles in its texture"></video>
|
||||
<figcaption>Flipbooks</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/lighting-and-effects/light-sources/Showcase.jpg" alt="A far out view of a diner bulding in the middle of a nighttime city. The diner is utilizing several local light sources." width="800" />
|
||||
<figcaption>Light sources</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
If you want to continue learning through step-by-step lessons, check out the following use case tutorials:
|
||||
|
||||
- [Create laser beams](../../use-case-tutorials/vfx/laser-traps-with-beams.md)
|
||||
- [Create waterfalls](../../use-case-tutorials/vfx/create-waterfalls.md)
|
||||
- [Create volcanoes](../../use-case-tutorials/vfx/create-volcanoes.md)
|
||||
- [Enhance indoor lighting](../../use-case-tutorials/lighting/enhance-indoor-environments.md)
|
||||
- [Enhance outdoor lighting](../../use-case-tutorials/lighting/enhance-outdoor-environments.md)
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: Test and save
|
||||
description: Explains how to playtest and save your carnival experience.
|
||||
next: /tutorials/curriculums/artist/next-steps
|
||||
prev: /tutorials/curriculums/artist/work-with-particle-emitters
|
||||
---
|
||||
|
||||
Now that your VFX art is complete, it's time to playtest your experience to verify that both particle emitters emit particles properly and with the correct visuals, then publish your work to Roblox so that you can save your progress.
|
||||
|
||||
## Playtest your art
|
||||
|
||||
It's important to playtest your experience often to ensure that your VFX art looks exactly the way you want it to display to your players. Studio offers several playtest options:
|
||||
|
||||
- **Test** – Starts the playtest by inserting your avatar at a spawn location.
|
||||
- **Test Here** – Starts the playtest by inserting your avatar in front of the camera's current position.
|
||||
- **Run** – Starts the playtest without inserting your avatar. Instead, the playtest begins at the current camera position and you can navigate around using the camera controls.
|
||||
|
||||
While all options are useful at different times, it's important to use a playtest option that inserts your avatar so that you can access the carnival ride and test your laser blast and explosion VFX.
|
||||
|
||||
To playtest your art:
|
||||
|
||||
1. In the top-left corner of Studio, verify that your playtest is set to either **Test** or **Test Here**, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
1. In the experience, walk up to the character under the **Play It** sign and press <kbd>E</kbd> to open up a menu. From that menu, click the **Join Game** button.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/test-and-play/start-game.png" width="70%" />
|
||||
|
||||
<Alert severity="warning">
|
||||
During the ride, you can't use your mouse to click parts of Studio. To use the mouse, press <kbd>1</kbd> on the keyboard.
|
||||
</Alert>
|
||||
|
||||
1. When the ride starts, left click to shoot the blaster at your enemies. Watch the cart's health at the top of the screen and try to make it to the treasure room at the end.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/test-and-play/game-health.png" width="70%" />
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
## Publish to Roblox
|
||||
|
||||
If you were to close Studio now, you would lose every edit you made to your experience. For this reason, it's important to publish your work to Roblox often to save your work and connect the experience to your account.
|
||||
|
||||
<Alert severity="info">
|
||||
It's recommended to publish to Roblox every ten minutes or after making a big change.
|
||||
</Alert>
|
||||
|
||||
1. In the top-left corner of your computer, click **File** → **Publish to Roblox**. The **Publish Game** window displays.
|
||||
|
||||
1. In the **Publish Game** window,
|
||||
|
||||
1. In the **Name** field, provide a name for your experience.
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> In the **Description** field, provide a summary of what a player can expect from the experience.
|
||||
1. In the **Devices** section, enable every device you want players to use to access your experience.
|
||||
1. At the bottom-right of the window, click the **Create** button.
|
||||
|
||||
Now that your experience is published and connected to your account, you can edit it from any computer!
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Saving after publishing</AlertTitle>
|
||||
Next time you want to save your work, just go to **File** ⟩ **Publish to Roblox** or use the hotkey combo (<kbd>Alt</kbd><kbd>P</kbd>/<kbd>⌥</kbd><kbd>P</kbd>).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
---
|
||||
title: Work with particle emitters
|
||||
description: Explains how to work with particle emitters to create special effects for a carnival experience.
|
||||
next: /tutorials/curriculums/artist/test-and-save
|
||||
prev: /tutorials/curriculums/artist/get-started
|
||||
---
|
||||
|
||||
**Particle emitters** create special effects by emitting 2D images, also referred to as particles, into the 3D world. By customizing how particles look and behave as they emit into the environment, you can create visual art that makes your experiences feel unique and captivating to potential players.
|
||||
|
||||
To demonstrate, check out the **Mansion of Wonder** template uses the following three images to create VFX art in different areas of the carnival ride:
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/particle-star.png" width="80%" /><br />
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Short-Arrow-Down.png" width="32" /><br />
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Rock-Video.mp4" width="100%" style={{marginTop: 0}}></video></center>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/particle-flame.png" width="80%" /><br />
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Short-Arrow-Down.png" width="32" /><br />
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Fire-Video.mp4" width="100%" style={{marginTop: 0}}></video></center>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/particle-portal.png" width="80%" /><br />
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Short-Arrow-Down.png" width="32" /><br />
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/Portal-Video.mp4" width="100%" style={{marginTop: 0}}></video></center>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Using the exact same techniques, in this chapter, let's explore how to find the carnival ride's laser blast and explosion particle emitters, then customize their properties until you transform the bland starting particles into your own VFX creations.
|
||||
|
||||
## Find sample emitters
|
||||
|
||||
This lesson's template provides two basic particle emitters that players interact with during the carnival ride:
|
||||
|
||||
- One for the **laser blast** that flies through the air when players shoot their blaster.
|
||||
- Another for the **explosion** when the laser blast hits a ghost or slime enemy.
|
||||
|
||||
Both particle emitters start with simple white particles so that you can see your progress in real time as you customize their visual appearance and behavior and visual appearance throughout this chapter.
|
||||
|
||||
To find the sample particle emitters:
|
||||
|
||||
1. Using the following camera controls, move your camera to get a good view of the two particle emitter stands near the front of the mansion. The first particle emitter you will customize is the laser blast particle emitter on the left with the white sparkles.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button to rotate around the selected object.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Zoom**</td>
|
||||
<td>Use your mouse's scroll wheel.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Focus**</td>
|
||||
<td>Press <kbd>F</kbd> to focus on a the selected object.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/particle-holder.png" width="80%" />
|
||||
|
||||
1. Find the laser blast particle emitter.
|
||||
|
||||
1. In the **Explorer** window, go to the search bar, then type **BlastParticles**.
|
||||
1. In your search results, click the particle emitter result.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/select-blaster-shot.png" width="40%" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, toggle them on from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
## Customize laser blast
|
||||
|
||||
Whenever you select something in the 3D viewport or the **Explorer** window, the **Properties** window updates to display all properties that you can directly customize within Studio's interface. For example, when you selected the **BlastParticles** particle emitter in the previous section, the **Properties** window updated with all properties for that specific particle emitter.
|
||||
|
||||
To introduce you to customizing properties, let's start with customizing the color and image of the laser blast's particles. As you get more comfortable with this process, you can experiment with more properties.
|
||||
|
||||
### Color
|
||||
|
||||
A particle emitter's `Class.ParticleEmitter.Color|Color` property tints each particle's texture to either a specific hue or a gradient of color from the time it emits to the time it fades. This lesson focuses on setting particles to a specific hue, but you can learn more about how to set them to a gradient in the [Particle emitters](../../../effects/particle-emitters.md#color) guide after you finish the tutorial.
|
||||
|
||||
To tint the laser blast particles with a new color:
|
||||
|
||||
1. In the **Properties** window, find the **Color** property, then double-click the light grey box to its right. A pop-up **Colors** window displays.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/select-color.png" />
|
||||
|
||||
1. In the **Colors** window, select a color and then click the **OK** button. For example, if you set the **Color** property to `202, 210, 20`, your particles will look like the following image.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/show-color-change.png" width="30%" />
|
||||
|
||||
### Texture
|
||||
|
||||
A particle emitter's `Class.ParticleEmitter.Texture|Texture` property tells the particle emitter what picture, also referred to as **textures**, to emit into the environment. Textures can be any 2D image you want, such as a symbol or even something you've drawn. For example, the following video demonstrates how changing a particle emitter's texture creates entirely different effects.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/particle-texture-example.mp4" width="80%"></video>
|
||||
|
||||
To set the laser blast particles to a new texture:
|
||||
|
||||
1. Pick one of the following images to use as the particle emitter's texture, then copy the assetID number beneath the image. You can return to this starter pack of textures when you are ready to customize the explosion.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/star-1.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5860841663`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/swirl.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857851812`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/heart.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857851618`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/circle.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://6711256324`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/smoke.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5833235272`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/star-2.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://6772783963`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/spark.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5833323391`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/circle-2.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857892330`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/scratch.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857892405`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/trace.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857931724`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/triangle.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5857851618`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<center><img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/star-3.png" width="50%"/></center>
|
||||
<figcaption>`rbxassetid://5860841737`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
1. In the **Properties** window, find the **Texture** property, then click on it to highlight the text.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/show-texture-property.png" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the same properties as the example image, return to the **Explorer** window and reselect the **BlastParticles** particle emitter.
|
||||
</Alert>
|
||||
|
||||
1. Paste your texture's assetID by pressing <kbd>Ctrl</kbd><kbd>V</kbd> or <kbd>⌘</kbd><kbd>V</kbd>, depending on your computer, then press **Enter**. Your particle emitter now emits particles that look like your texture. For example, if you use the `5833323391` texture, your particles will look like the example below.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/using-textures/texture-changed.png" width="40%" />
|
||||
|
||||
## Customize explosion
|
||||
|
||||
Now that you have custom VFX art for your laser blast, you can move on to customizing the explosion whenever the laser blast hits a sinister slime or ghastly ghost. To verify that you're customizing the explosion's properties instead of the laser blast, make sure you select the explosion particle emitter so that the **Properties** window updates with its specific properties:
|
||||
|
||||
1. In the **Explorer** window, go to the search bar, then type **ExplosionParticles**.
|
||||
1. In your search results, click the particle emitter result.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/particle-emitters/select-explosion.png" width="40%" />
|
||||
|
||||
<Alert severity="info">
|
||||
Even though this section focuses on customizing the size and speed of the explosion's particles, feel free to use your previous learnings to customize their color and texture too.
|
||||
</Alert>
|
||||
|
||||
### Size
|
||||
|
||||
A particle emitter's `Class.ParticleEmitter.Size|Size` property changes the size of each particle's texture to either a specific size or size that changes from the time it emits to the time it fades. This lesson focuses on setting particles to a specific size, but you can learn more about how to set them to a changing size in the [Particle emitters](../../../effects/particle-emitters.md#size) guide after you finish the tutorial.
|
||||
|
||||
To set the explosion particles to a new size:
|
||||
|
||||
1. In the **Properties** window, find the **Size** property.
|
||||
1. For best results, set **Size** to a number between `1` and `10`.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/change-explode-size.png" width="40%" />
|
||||
|
||||
When it comes to particles, there isn't a "one size fits all"; different particle textures look better at various sizes. To demonstrate, review the following video to see how a star texture transforms as the `Class.ParticleEmitter.Size|Size` value increases.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/newsizecomparison.mp4" width="80%"></video>
|
||||
|
||||
### Speed
|
||||
|
||||
A particle emitter's `Class.ParticleEmitter.Speed|Speed` property changes the rate at which particles emit from the particle emitter. In other words, you can change this property to ensure that particles either burst out quickly or creep out slowly.
|
||||
|
||||
To set the explosion particles to a new speed:
|
||||
|
||||
1. In the **Properties** window, find the **Speed** property, then set it to a number between `10` (slow) and `100` (fast).
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/change-speed.png" width="40%" />
|
||||
|
||||
1. Using your learnings from the previous chapter, playtest the experience to see your particles in action. If anything doesn't look right, you can go back and make changes to your design.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/explosion-final.mp4" width="80%"></video>
|
||||
|
||||
## Additional properties
|
||||
|
||||
Your laser blast and explosion VFX art are almost done! While you've customized a few particle emitter properties, there are many more you can modify to personalize your work. The following sections highlight a few, but feel free to also experiment with any that aren't listed here.
|
||||
|
||||
### LightEmission
|
||||
|
||||
The `Class.ParticleEmitter.LightEmission|LightEmission` property makes particles glow. Higher values of this property can make particles glow even in environments with low lighting.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/light-emission-0.jpeg" />
|
||||
<figcaption>0</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/light-emission-0.5.jpeg" />
|
||||
<figcaption>0.5</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/light-emission-1.0.jpeg" />
|
||||
<figcaption>1</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### RotSpeed
|
||||
|
||||
The `Class.ParticleEmitter.RotSpeed|RotSpeed` property makes particles rotate over time. To demonstrate how this works, review the following video to see how the particles' straight line texture changes rotation as the `Class.ParticleEmitter.RotSpeed|RotSpeed` property increases.
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/rotation-speed-new.mp4" width="80%"></video>
|
||||
|
||||
### Lifetime
|
||||
|
||||
The `Class.ParticleEmitter.Lifetime|Lifetime` property changes how long in seconds particles are visible once they emit from the particle emitter. Depending on your VFX art, you may want your particles to only be visible for a moment (`0.5` seconds), or for them to linger in the air (`2` seconds).
|
||||
|
||||
<video controls src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/lifetime-new.mp4" width="80%"></video>
|
||||
|
||||
### Randomized numbers
|
||||
|
||||
Randomizing properties can make particles feel less repetitive. You can add randomization to some particle emitters properties, such as `Class.ParticleEmitter.Lifetime|Lifetime` and `Class.ParticleEmitter.RotSpeed|RotSpeed`, by adding a minimum and maximum value range for the respective property.
|
||||
|
||||
To randomize numbers:
|
||||
|
||||
1. Click the small arrow next to the property's name. The property expands.
|
||||
|
||||
<img src="../../../assets/education/build-it-play-it-mansion-of-wonder/make-final-touches/show-random-options.png" width="40%" />
|
||||
|
||||
1. Set **Min** to the minimum value you want your particles to have.
|
||||
1. Set **Max** to the maximum value you want your particles to have.
|
||||
|
||||
Using your new knowledge with particle emitters, take some time to improve your VFX art. When you are ready, continue to the next section to learn more about playtesting and how to save your work to Roblox.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
title: AI Assistant
|
||||
description: Explains how use AI Assistant prompts to add code to your obby.
|
||||
next: /tutorials/curriculums/building/test-and-save
|
||||
prev: /tutorials/curriculums/building/work-with-parts
|
||||
---
|
||||
|
||||
<figure>
|
||||
<iframe width="800" height="450" src="https://www.youtube-nocookie.com/embed/PqB6DC_kxHM" title="YouTube video player" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</figure><br />
|
||||
|
||||
**Assistant** is Roblox's AI helper that can help you create and code your experiences. In this chapter, let's walk through how to open Assistant in Studio, write requests that change part behavior, and playtest your results to see if you need to make any changes to the Assistant's code sample.
|
||||
|
||||
## Open Assistant
|
||||
|
||||
Assistant is available in the top-right corner of this webpage, as well as directly in Studio. This second placement allows Assistant to directly reference everything that makes up your experience, then make suggestions on how you can add or edit 3D objects and scripts for your specific gameplay goals.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/Mezzanine-Assistant.png" alt="Assistant button indicated on the right side of Studio's mezzanine bar." width="800" />
|
||||
|
||||
## Create prompt
|
||||
|
||||
Once Assistant is open in Studio, it can reference all of your platforms and add scripts to change their behavior, such as:
|
||||
|
||||
- Turning a different color every time a player touches the part.
|
||||
- Moving upward, waiting for 3 seconds, then moving back to its previous position.
|
||||
- Reducing the player's health the longer they stay on the part.
|
||||
|
||||
To demonstrate how this process works, this section of the tutorial shows you how to work with Assistant to make your first platform sparkle whenever a player jumps onto it.
|
||||
|
||||
To create a prompt for this behavior:
|
||||
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> Rename your first platform so that Assistant knows which part to create a script for.
|
||||
1. In the viewport, select your first platform.
|
||||
1. In the **Explorer** window, right-click the active object, then select Rename from the contextual menu.
|
||||
1. Type `GlitterPart`, then press **Enter**.
|
||||
1. In the **Assistant** window, navigate to the **Enter request here** input, then type the following: `Add a new script that creates yellow sparkles while GlitterPart is touched`.
|
||||
|
||||
<Alert severity="warning">
|
||||
You can use a different color, but using different words might change the results.
|
||||
</Alert>
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/createPrompt-2.png" alt="" width="40%" />
|
||||
|
||||
1. Press **Enter**. Assistant starts generating a code sample, then a window displays with a suggested code sample that's similar to the following:
|
||||
|
||||
``` lua
|
||||
local part = script.Parent
|
||||
local sparkles = nil
|
||||
local touchCount = 0
|
||||
|
||||
local function createSparkles()
|
||||
if not sparkles then
|
||||
sparkles = Instance.new("Sparkles")
|
||||
sparkles.Color = Color3.new(1, 1, 0) -- Yellow
|
||||
sparkles.Parent = part
|
||||
end
|
||||
end
|
||||
|
||||
local function removeSparkles()
|
||||
if sparkles then
|
||||
sparkles:Destroy()
|
||||
sparkles = nil
|
||||
end
|
||||
end
|
||||
|
||||
part.Touched:Connect(function(hit)
|
||||
touchCount = touchCount + 1
|
||||
createSparkles()
|
||||
end)
|
||||
|
||||
part.TouchEnded:Connect(function(hit)
|
||||
touchCount = touchCount - 1
|
||||
if touchCount <= 0 then
|
||||
removeSparkles()
|
||||
touchCount = 0
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
Assistant is constantly learning, so it might not always produce the same results for the exact same request.
|
||||
</Alert>
|
||||
|
||||
1. Click **Accept**. As you learn more about coding with Luau, it will be easier to review Assistant's code samples to make sure they work for your use cases.
|
||||
|
||||
There is more than one right way to make any particular request, but every word counts. For example, the following chart shows how even slightly different word choices to the original prompt can produce different results.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Request Variationn**</th>
|
||||
<th>**Possible Results**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Create yellow sparkles while GlitterPart is touched.`</td>
|
||||
<td>Assistant might show you the code, but not make a new script. It might also add the code to a random existing script.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Make purple sparkles when the part named GlitterPart is stepped on.`</td>
|
||||
<td>Assistant might make sparkles before GlitterPart is stepped on, or make only a few sparkles.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Make GlitterPart sparkle`</td>
|
||||
<td>Instead of adding a script, Assistant will just add a particle emitter (the object which makes sparkles) to GlitterPart. Which can be good if you want it to sparkle at all times.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
The fewer details you provide, the more unpredictable the results. For example, possible results for the prompt Make parts sparkle when you touch them may include:
|
||||
|
||||
- The resultant script not working at all.
|
||||
- Only a single random part working.
|
||||
- Parts sparkling before they are touched.
|
||||
|
||||
If you make a request and do not get the results you want, rephrase the request and try again.
|
||||
|
||||
<Alert severity="info">
|
||||
Accidentally accept a suggestion that you didn't want? You can undo the change using one of the following methods:
|
||||
|
||||
- For non-script changes, press <kbd>Ctrl</kbd><kbd>Z</kbd> or <kbd>⌘</kbd><kbd>Z</kbd>, depending on your computer.
|
||||
- If Assistant made a script, click on the script's name in the **Explorer** window, then press <kbd>Delete</kbd>.
|
||||
</Alert>
|
||||
|
||||
## Playtest results
|
||||
|
||||
Although Assistant is constantly learning and getting better, it sometimes produces results that are different than you intended. You must always check the results of its suggestions to make sure they work as intended.
|
||||
|
||||
To playtest Assistant's results:
|
||||
|
||||
1. In the top-left corner of Studio, keep the default **Test** playtest option, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
1. In the experience, jump onto the first platform. If the code sample works correctly, yellow sparkles appear when your character touches the part.
|
||||
1. When you're done verifying the results, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> Return to the Assistant window, then press the thumbs up or thumbs down icon to let Assistant know if the code sample worked as you intended.
|
||||
|
||||
Now that you know how to code with Assistant, try creating your own prompts to change how parts behave in your obby. When you're happy with your creation, move onto the next section of the tutorial to test and save your work to Roblox.
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
title: Get started
|
||||
description: Explains how to get started with opening a project.
|
||||
next: /tutorials/curriculums/building/work-with-parts
|
||||
prev: /tutorials/curriculums/building/
|
||||
---
|
||||
|
||||
Welcome to your first beginner building lesson in Studio! To get started, let's walk through how to open the sample template, customize Studio's layout for your tasks, and playtest the core gameplay of the experience in which players race through an obstacle course (obby) until they reach the final platform.
|
||||
|
||||
## Open template
|
||||
|
||||
Templates are pre-made 3D worlds that you can use as the base for your own experiences. To introduce you to building in Studio, Roblox has created the **Classic Obby** template with everything necessary for the experience to work properly, such as the code to run the gameplay and a simple skybox that you can customize at a later time.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/ObbyThumbnail.png" width="70%" />
|
||||
|
||||
<Alert severity="info">
|
||||
For a full list of Roblox-provided templates, see [Templates](../../../resources/templates.md).
|
||||
</Alert>
|
||||
|
||||
Click the following button to open the **Classic Obby** template:
|
||||
|
||||
<UseStudioButton variant="" buttonTextTranslationKey="Action.EditInStudio" placeId="203812057" universeId="93412282" />
|
||||
|
||||
## Customize workspace
|
||||
|
||||
Roblox Studio launches with extra windows open that you don't need for this lesson. You can close these extra windows to create more space for what you're currently working on.
|
||||
|
||||
To customize the workspace for your coding task:
|
||||
|
||||
1. Close each window on the **left** of the 3D view by clicking the **×** in the window's upper-right corner. If you don't see anything to close on the left, go to the next step.
|
||||
|
||||
2. Leave the **Explorer** and **Properties** windows open on the right side of Studio. If you have done each step correctly, Studio looks like the following image.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/Studio-Setup.jpg" width="960" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, open them from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
## Playtest the experience
|
||||
|
||||
Before you start building your obby, it's important to take a moment to playtest the template so you can see how the gameplay works. When players join the experience, they begin in an obstacle course that starts easy and becomes more difficult, including platforms that move, push players high in the air, or set their character's health to zero.
|
||||
|
||||
To playtest the experience's gameplay:
|
||||
|
||||
1. In the top-left corner of Studio, keep the default **Test** playtest option, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
<Alert severity="info">
|
||||
There are a variety of additional playtest options that you will explore in a later chapter of the tutorial.
|
||||
</Alert>
|
||||
|
||||
1. In the experience, use the following player controls to try to avoid the obstacles and reach the final platform at the end of the course.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> or arrow keys</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button and look around.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Pan**</td>
|
||||
<td>Hold the middle mouse button to drag your camera around.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
Now that you are familiar with the gameplay, it's time to start building your own obby using basic parts!
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: Intro to building
|
||||
description: Learn the basics of building experiences.
|
||||
next: /tutorials/curriculums/building/get-started
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<img src="../../../assets/tutorials/landing/BuildingThumbnail.png" width="90%" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
Using the [Classic Obby](https://www.roblox.com/games/203812057/Classic-Obby) template as a guide, this tutorial acts as an introduction to building in Roblox Studio using basic parts and AI Assistant. As you follow each chapter, you will learn how to create an obstacle course (obby) where players jump across platforms and avoid obstacles until they reach the final platform.
|
||||
|
||||
This lesson is intended for readers who are **brand new** to building with parts and working in Studio. If you are familiar with both, try another beginner lesson plan or jump straight to [building your first experience](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<Alert severity = 'info'>
|
||||
If you don't have a Roblox account, Studio active on your device, or familiarity with Studio's UI, see the [Studio lesson](../studio/index.md).
|
||||
</Alert>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Get started</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [open and customize](get-started.md) the template's workspace so that you can focus on building your obby.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Work with parts</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [create and design platforms](work-with-parts.md) for your obby using basic part shapes.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Code with AI Assistant</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [modify part behavior](code-with-assistant.md) with code from AI Assistant.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Test and save</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [playtest your obby](test-and-save.md) and publish your work to Roblox.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 5 - Next steps</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn where you can [find more tutorials](next-steps.md) about building experiences with parts and meshes.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: Next steps
|
||||
description: Explains ways to continue learning about building in Studio.
|
||||
next: /tutorials/curriculums/coding/
|
||||
prev: /tutorials/curriculums/building/test-and-save
|
||||
---
|
||||
|
||||
Congratulations on building your first obby with Roblox Studio! Now that you have experience creating and customizing basic parts with unique visuals and behavior, you can share your creation with others and extend your skills by learning about Studio's additional building features.
|
||||
|
||||
## Share with others
|
||||
|
||||
When you first publish an experience, it's automatically set to **private** so that you are the only one who can play it. However, creating on Roblox is always more fun when you can share what you make with others, so let's set up your experience so that they can play too.
|
||||
|
||||
To make your experience public:
|
||||
|
||||
1. Using the steps from the last chapter, publish your experience so others can see your latest changes.
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Experience Settings**.
|
||||
|
||||
1. In the settings window:
|
||||
1. Go to the left bar and select the **Permissions** tab. New settings display.
|
||||
1. Set **Playability** to **Public**.
|
||||
|
||||
1. Click the **Save** button. Now everyone on Roblox can play your experience!
|
||||
|
||||
## Continue learning
|
||||
|
||||
Basic parts are an excellent start for building in the 3D space, but Studio offers many additional features to create environments in your experiences, including:
|
||||
|
||||
- [Materials](../../../parts/materials.md) that change the visual appearance and physical properties of parts.
|
||||
- [Terrain Editor](../../../parts/terrain.md) that generates realistic terrain environments, such as mountains, grass-covered hills, or a flat desert.
|
||||
- [Textures and decals](../../../parts/textures-decals.md) that decorate parts with 2D images that either scale or stretch, respectively.
|
||||
- [Solid modeling operations](../../../parts/solid-modeling.md) that join parts together in unique ways to form more complex shapes known as unions or intersections.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/building-lesson/materials.png" alt="Two rows of optional materials." />
|
||||
<figcaption>Materials</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/studio/terrain-editor/Fill-Region.jpg" alt="Region filled with Salt material" />
|
||||
<figcaption>Salt terrain</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/building-lesson/Texture-On-Surface.jpg" alt="The same blue texture repeated eight times on a block part." />
|
||||
<figcaption>Hexagon texture on a part</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/solid-modeling/Negate-Result.jpg" alt="Negated part subtracted from cylinder" />
|
||||
<figcaption>Solid modeling union</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: Test and save
|
||||
description: Explains how to playtest and save your obby experience.
|
||||
next: /tutorials/curriculums/building/next-steps
|
||||
prev: /tutorials/curriculums/building/code-with-assistant
|
||||
---
|
||||
|
||||
Now that your obstacle course is complete, it's time to playtest your experience to verify that players are able to make it to your final platform, then publish your work to Roblox so that you can save your progress.
|
||||
|
||||
## Playtest your obby
|
||||
|
||||
It's important to playtest your experience often to ensure that your obby is possible to complete as expected. Studio offers several playtest options:
|
||||
|
||||
- **Test** – Starts the playtest by inserting your avatar at a spawn location.
|
||||
- **Test Here** – Starts the playtest by inserting your avatar in front of the camera's current position.
|
||||
- **Run** – Starts the playtest without inserting your avatar. Instead, the playtest begins at the current camera position and you can navigate around using the camera controls.
|
||||
|
||||
While all options are useful at different times, it's important to use a playtest option that inserts your avatar so that you can test the jump distance between platforms.
|
||||
|
||||
To playtest your obby:
|
||||
|
||||
1. In the top-left corner of Studio, verify that your playtest is set to either **Test** or **Test Here**, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
1. In the experience, try to avoid the obstacles and reach the final platform at the end of the course.
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
## Publish to Roblox
|
||||
|
||||
If you were to close Studio now, you would lose every edit you made to your experience. For this reason, it's important to publish your work to Roblox often to save your work and connect the experience to your account.
|
||||
|
||||
<Alert severity="info">
|
||||
It's recommended to publish to Roblox every ten minutes or after making a big change.
|
||||
</Alert>
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Publish to Roblox** to open the publishing window.
|
||||
|
||||
<Alert severity="warning">
|
||||
If **Publish to Roblox** is greyed out, make sure you stopped your playtest.
|
||||
</Alert>
|
||||
|
||||
1. In the **Publish Game** window,
|
||||
|
||||
1. In the **Name** field, provide a name for your experience.
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> In the **Description** field, provide a summary of what a player can expect from the experience.
|
||||
1. In the **Devices** section, enable every device you want players to use to access your experience.
|
||||
1. At the bottom-right of the window, click the **Create** button.
|
||||
|
||||
Now that your experience is published and connected to your account, you can edit it from any computer!
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Saving after publishing</AlertTitle>
|
||||
Next time you want to save your work, just go to **File** ⟩ **Publish to Roblox** or use the hotkey combo (<kbd>Alt</kbd><kbd>P</kbd>/<kbd>⌥</kbd><kbd>P</kbd>).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: Work with parts
|
||||
description: Explains how to insert and customize parts for your obby.
|
||||
next: /tutorials/curriculums/building/code-with-assistant
|
||||
prev: /tutorials/curriculums/building/get-started
|
||||
---
|
||||
|
||||
**Parts** are basic 3D shapes that Roblox provides out-of-the-box for building in the 3D space. In this chapter, let's explore how to insert parts of all shapes and sizes, change their position and rotation for your obby, then anchor them in the air so that they don't fall due to Studio's gravity.
|
||||
|
||||
## Insert parts
|
||||
|
||||
There are five different part shapes that you can insert from the toolbar to build environments in your experiences: **block**, **sphere**, **cylinder**, **wedge**, and **corner wedge**. While you can join these part shapes together in unique ways to form more complex shapes, this lesson focuses on keeping the shapes as-is for your obby's platforms.
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Basic-Part-Block.png" alt="A single Block part." />
|
||||
<figcaption><center>Block</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Basic-Part-Sphere.png" alt="A single Sphere part." />
|
||||
<figcaption><center>Sphere</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Basic-Part-Cylinder.png" alt="A single Cylinder part." />
|
||||
<figcaption><center>Cylinder</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Basic-Part-Wedge.png" alt="A single Wedge part." />
|
||||
<figcaption><center>Wedge</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Basic-Part-Corner-Wedge.png" alt="A single Corner Wedge part." />
|
||||
<figcaption><center>Corner Wedge</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
To insert a part for your first platform:
|
||||
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> When you insert parts, they appear at the exact center of your camera view. Select the **START** platform, then press <kbd>F</kbd> to focus the camera on this part so your additional platforms insert near the start of the obby.
|
||||
|
||||
<Alert severity="info">
|
||||
If you want more control over where the part inserts into the 3D space, use your mouse to zoom in and center your view where you want to insert your part.
|
||||
</Alert>
|
||||
|
||||
1. In the **Home** or **Model** tab's toolbar, click the small arrow next to the **Part** inserter tool to reveal the dropdown.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/part-picker.png" alt="A close-up view of the part picker." width="800" />
|
||||
|
||||
1. From the dropdown menu, select the part shape you want for your first platform. The part displays at the exact center of your camera view.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/insert-2.png" alt="" width="40%" />
|
||||
|
||||
1. Using this process, insert additional shape types until you have as many platforms as you want for your obby.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/insert-3.png" alt="" width="40%" />
|
||||
|
||||
## Customize course
|
||||
|
||||
Now that you have platforms for your obby, it's time to customize their position, size, and rotation to create a jumping puzzle for the start of your obby. To do this, you will use three different object tools:
|
||||
|
||||
- **Move** — Changes your object's **position**.
|
||||
- **Scale** — Changes your object's **size**.
|
||||
- **Rotate** — Changes your object's **orientation**.
|
||||
|
||||
Each of these tools includes visual handles you can use to move, scale, or rotate your object using Studio's coordinate system for the 3D space. When you click and drag one of the handles, the object moves, scales, or rotates, respectively.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Transform-Move-SM.png" alt="Move draggers shown on part in 3D viewport." />
|
||||
<figcaption>Move tool handles</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Transform-Scale-SM.png" alt="Scale draggers shown on part in 3D viewport." />
|
||||
<figcaption>Scale tool handles</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/modeling/parts/Transform-Rotate-SM.png" alt="Rotate draggers shown on part in 3D viewport." />
|
||||
<figcaption>Rotate tool handles</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To customize your course for your obby's starting jumping puzzle:
|
||||
|
||||
1. Change the **position** of your first platform.
|
||||
1. In the viewport, click on the part shape that you want for your first platform.
|
||||
|
||||
1. In the toolbar, select the **Move** tool. A set of handles display around the part.
|
||||
|
||||
<img src="../../../assets/education/general/Move-Tool.png" width="328" alt="Move tool highlighted." />
|
||||
|
||||
1. Click and drag one of the handles until your part is at a good jump distance away from where players join the experience.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/customize-1.png" alt="" width="60%" />
|
||||
|
||||
<Alert severity="info">
|
||||
Keep in mind that the jump distance between platforms is important. If you make it too difficult from the start, players may quit instead of continuing to play.
|
||||
</Alert>
|
||||
|
||||
1. Change the **size** of your first platform.
|
||||
|
||||
1. In the toolbar, select the **Scale** tool. A set of handles display around the part.
|
||||
|
||||
<img src="../../../assets/education/general/Scale-Tool.png" width="328" alt="Scale tool highlighted." />
|
||||
|
||||
1. Click and drag one of the handles until your part is a different size.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/customize-2.png" alt="" width="60%" />
|
||||
|
||||
1. Change the **orientation** of your first platform.
|
||||
1. In the toolbar, select the **Rotate** tool. A set of handles display around the part.
|
||||
|
||||
<img src="../../../assets/education/general/Rotate-Tool.png" width="328" alt="Rotate tool highlighted." />
|
||||
|
||||
1. Click and drag one of the handles until your part has a different rotation.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/customize-3.png" alt="" width="60%" />
|
||||
|
||||
1. Using the following camera controls, check your part's position and orientation from multiple angles to make sure that players would be able to jump to the first platform.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button to rotate around the selected object.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Zoom**</td>
|
||||
<td>Use your mouse's scroll wheel.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Focus**</td>
|
||||
<td>Press <kbd>F</kbd> to focus on a the selected object.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="info">
|
||||
If the camera doesn't move, click anywhere in the viewport, then try again.
|
||||
</Alert>
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/customize-4.png" alt="" width="60%" />
|
||||
|
||||
1. Repeat this process for the rest of your platforms.
|
||||
|
||||
<img src="../../../assets/tutorials/building-lesson/customize-5.png" alt="" width="60%" />
|
||||
|
||||
## Anchor platforms
|
||||
|
||||
If you were to playtest your experience now, all of your new platforms would immediately fall and disappear. This is because the Roblox engine simulates gravity as soon as the experience runs, allowing the physical force to push objects down.
|
||||
|
||||
<video controls src="../../../assets/tutorials/building-lesson/anchor-demo.mp4" width="70%"></video>
|
||||
|
||||
To make sure your platforms stay at their set position and orientation when the experience runs, you must anchor them within the 3D space using the **Anchor** tool. In addition to preventing gravity from pushing the platforms down, anchoring your platforms ensures they will stay in place when players and other objects bump into them.
|
||||
|
||||
To anchor your platforms:
|
||||
|
||||
1. While holding the <kbd>Shift</kbd> key, select all of your new parts.
|
||||
1. In the **Home** or **Model** tab, toggle on **Anchor**.
|
||||
|
||||
<img src="../../../assets/education/general/Anchor-Tool.png" width="800" alt="Anchor tool highlighted." />
|
||||
|
||||
Now that you have all of the platforms in place for your obby's jumping puzzle, it's time to add code with AI Assistant to modify part behavior.
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
title: Customize strings
|
||||
description: Explains how to customize the strings for your story.
|
||||
next: /tutorials/curriculums/coding/next-steps
|
||||
prev: /tutorials/curriculums/coding/test-and-save
|
||||
---
|
||||
|
||||
With the first sentence of your story done and working in Studio, it's time to customize your string with punctuation and line breaks. Once you are happy with how it looks and reads to players, you can add more sentences and finish the rest of your story.
|
||||
|
||||
## Add punctuation
|
||||
|
||||
When you playtest your experience, your first sentence doesn't include any punctuation to let players know the sentence is complete. To fix this, you can add another string using the same process of concatenation to connect your words with your punctuation.
|
||||
|
||||
To add punctuation:
|
||||
|
||||
1. On the same line as the **story** variable, type two periods.
|
||||
1. Add another string with a punctuation mark, such as a period or exclamation point, then an extra space so your next sentence doesn't start immediately after the punctuation mark.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! ")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
## Add line breaks
|
||||
|
||||
A line break moves the cursor to the next line of text. This is helpful when you want every sentence to start from the left-side of the page, and enhance your story's readability in the giant book.
|
||||
|
||||
To add a line break:
|
||||
|
||||
1. On the same line as the **story** variable, type `\n`.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! \n")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> You can add more than one line break by adding as many `\n` as you need. For example, the following code sample adds in three line breaks.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! \n\n\n")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
## Add more sentences
|
||||
|
||||
Now that your first sentence includes punctation and any applicable line breaks, you can add more sentences to your story and ask players more questions for your placeholder words.
|
||||
|
||||
To add more sentences:
|
||||
|
||||
1. Under your first variable, create a new variable for your second placeholder and ask a question related to what type of placeholder word it is. For example:
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
local food1
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! \n\n\n")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
1. Using the same process as your first variable, set the variable and use the provided `storyMaker:GetInput()` method that displays your questions in the giant book. For example:
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
local food1 = storyMaker:GetInput("What do you like to eat?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! \n\n\n")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
1. Combine the first string of the story with the player's answer using `..`.
|
||||
1. On the same line as the **story** variable, add two periods.
|
||||
1. Add a space, then type the name of the variable that stores the player's second answer.
|
||||
1. Add two more periods, then finish the sentence with a punctuation mark.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
local food1 = storyMaker:GetInput("What do you like to eat?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1 .. "! \n\n\n" .. "Every morning, the wizard loves eating a giant bowl of honey roasted " .. food1 .. ". ")
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
1. Repeat this process for your entire story, then playtest your experience using the steps from the previous chapter to make sure your code works properly.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
title: Get started
|
||||
description: Explains how to get started with opening a project.
|
||||
next: /tutorials/curriculums/coding/work-with-variables
|
||||
prev: /tutorials/curriculums/coding/
|
||||
---
|
||||
|
||||
Welcome to your first beginner coding lesson in Studio! To get started, let's walk through how to open the sample template, customize Studio's layout for your tasks, and brainstorm the core gameplay's story that you will code throughout the tutorial.
|
||||
|
||||
## Open template
|
||||
|
||||
Templates are pre-made 3D worlds that you can use as the base for your own experiences. To introduce you to coding in Studio, Roblox has created the **Story Game** template with everything necessary for the experience to work properly, except for the code for your story.
|
||||
|
||||
<img src="../../../assets/tutorials/coding-lesson/StoryGameThumb.png" width="70%" />
|
||||
|
||||
<Alert severity="info">
|
||||
For a full list of Roblox-provided templates, see [Templates](../../../resources/templates.md).
|
||||
</Alert>
|
||||
|
||||
Click the following button to open the **Story Games** template:
|
||||
|
||||
<UseStudioButton variant="" buttonTextTranslationKey="Action.EditInStudio" placeId="118424838654294" universeId="7930204318" />
|
||||
|
||||
## Customize workspace
|
||||
|
||||
Roblox Studio launches with extra windows open that you don't need for this lesson. You can close these extra windows to create more space for what you're currently working on.
|
||||
|
||||
To customize the workspace for your coding task:
|
||||
|
||||
1. Close each window on the **left** of the 3D view by clicking the **×** in the window's upper-right corner. If you don't see anything to close on the left, go to the next step.
|
||||
|
||||
2. Leave the **Explorer** and **Properties** windows open on the right side of Studio. If you have done each step correctly, Studio looks like the following image.
|
||||
|
||||
<img src="../../../assets/education/general/Studio-Setup-Story-Games.jpg" width="960" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** and **Properties** windows, open them from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
## Brainstorm your story
|
||||
|
||||
Before you start coding, it's important to take a moment and write a story for your core gameplay where players can replace placeholder words with their own answers. The story can be silly, scary, mysterious, or about anything you want. Here are a few story ideas:
|
||||
|
||||
- Your dream vacation
|
||||
- A superhero's shopping trip
|
||||
- What a wizard eats for breakfast
|
||||
|
||||
The more details you include, the more options you have for placeholder words in your game. The sky is the limit, so have a good time!
|
||||
|
||||
To brainstorm your story:
|
||||
|
||||
1. Using a piece of paper or a text editor on your computer, write 2-3 sentences for the opening paragraph.
|
||||
1. In the first sentence, pick a single word that you want your players to replace, such as a noun, action, or character's name. For example:
|
||||
|
||||
_In a tree on a hill lives the great wizard **Nia**._
|
||||
|
||||
1. Replace that word with a placeholder for what type of word that you are going to ask the player for, such as thing, action, or name. Because you likely are going to ask the player for more than one noun or adjective, it's recommended to number the placeholder, such as thing1, action1, or name1.
|
||||
|
||||
_In a tree on a hill lives the great wizard **name1**._
|
||||
|
||||
1. Repeat this process for every sentence of your story.
|
||||
|
||||
_In a tree on a hill lives the great wizard **name1**. Every morning, the wizard loves eating a giant bowl of honey roasted **food1**. Unless it's a Saturday, then the wizard likes to go to a local restaurant called **building1** with their dear friend **name2**. They always order at least one **food2**, of course!_
|
||||
|
||||
Now that you have a story, it's time to turn it into code!
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: Intro to coding
|
||||
description: Learn the basics of coding for experiences.
|
||||
next: /tutorials/curriculums/coding/get-started
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<img src="../../../assets/tutorials/landing/CodingThumbnail.png" width="90%" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
Using the [Story Games](https://www.roblox.com/games/2546610365/Roblox-Creator-Challenge) sample as a guide, this tutorial acts as an introduction to coding in Roblox Studio using the Luau programming language. As you follow each chapter, you will learn how to create a story game where players can replace placeholder words with their own answers.
|
||||
|
||||
This lesson is intended for readers who are **brand new** to coding concepts and working in Studio. If you are familiar with both, try another beginner lesson plan or jump straight to [building your first experience](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<Alert severity = 'info'>
|
||||
If you don't have a Roblox account, Studio active on your device, or familiarity with Studio's UI, see the [Studio lesson](../studio/index.md).
|
||||
</Alert>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Get started</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [open and customize](get-started.md) the template's workspace so that you can work with your main script.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Work with variables</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [use variables](work-with-variables.md) to ask players questions and use their answers within your story.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Test and save</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [playtest your story](test-and-save.md) and publish your work to Roblox.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Customize strings</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [style your code](customize-strings.md) with extra punctuation and line breaks.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 5 - Next steps</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn where you can [find more tutorials](next-steps.md) about coding for experiences.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
title: Next steps
|
||||
description: Explains ways to continue learning about coding in Studio.
|
||||
next: /tutorials/curriculums/artist/
|
||||
prev: /tutorials/curriculums/coding/customize-strings
|
||||
---
|
||||
|
||||
Congratulations on completing your first code sample with Roblox Studio! Now that you have experience working with variables, you can share your work with others and extend your skills by learning about Studio's additional coding features.
|
||||
|
||||
## Share with others
|
||||
|
||||
When you first publish an experience, it's automatically set to **private** so that you are the only one who can play it. However, creating on Roblox is always more fun when you can share what you make with others, so let's set up your experience so that they can play too.
|
||||
|
||||
To make your experience public:
|
||||
|
||||
1. Using the steps from the **Test and save** chapter, publish your experience so others can see your latest changes.
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Experience Settings**.
|
||||
|
||||
1. In the settings window:
|
||||
1. Go to the left bar and select the **Permissions** tab. New settings display.
|
||||
1. Set **Playability** to **Public**.
|
||||
|
||||
1. Click the **Save** button. Now everyone on Roblox can play your experience!
|
||||
|
||||
## Continue learning
|
||||
|
||||
Variables are an excellent start in your coding journey, but Luau includes additional fundamentals that are important to understand to add custom behavior to your experiences, including:
|
||||
|
||||
- [Script types and locations](../../../scripting/locations.md)
|
||||
- [Services](../../../scripting/services.md)
|
||||
- [Properties and attributes](../../../scripting/attributes.md)
|
||||
- [Events](../../../scripting/events/index.md)
|
||||
|
||||
It's recommended to continue learning with the [Coding fundamentals](../../fundamentals/coding-1/coding-fundamentals.md) series, then if you want to continue learning through step-by-step lessons, check out the following use case tutorials:
|
||||
|
||||
- [Create deadly lava](../../use-case-tutorials/scripting/basic-scripting/deadly-lava.md)
|
||||
- [Create fading traps](../../use-case-tutorials/scripting/basic-scripting/fading-trap.md)
|
||||
- [Create health pickups](../../use-case-tutorials/scripting/intermediate-scripting/create-a-health-pickup.md)
|
||||
- [Save player data](../../use-case-tutorials/scripting/intermediate-scripting/save-data.md)
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
title: Test and save
|
||||
description: Explains how to playtest and save your story experience.
|
||||
next: /tutorials/curriculums/coding/customize-strings
|
||||
prev: /tutorials/curriculums/coding/work-with-variables
|
||||
---
|
||||
|
||||
Now that the first sentence of your main story is complete and ready for players to write their own answer for the playerholder word, it's time to playtest your experience to verify that the code runs properly, then publish your work to Roblox so that you can save your progress.
|
||||
|
||||
## Playtest your code
|
||||
|
||||
It's important to playtest your experience often to ensure that your code works as expected. Studio offers several playtest options:
|
||||
|
||||
- **Test** – Starts the playtest by inserting your avatar at a spawn location.
|
||||
- **Test Here** – Starts the playtest by inserting your avatar in front of the camera's current position.
|
||||
- **Run** – Starts the playtest without inserting your avatar. Instead, the playtest begins at the current camera position and you can navigate around using the camera controls.
|
||||
|
||||
While all options are useful at different times, it's important to use a playtest option that inserts your avatar so that you can access the book and play through your story.
|
||||
|
||||
To playtest your code:
|
||||
|
||||
1. In the top-left corner of Studio, verify that your playtest is set to either **Test** or **Test Here**, then click the **Play** button. Studio enters playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/play-button.png" width="360" />
|
||||
|
||||
1. In the experience, use the following player controls to walk up to the pedestal, then press <kbd>E</kbd> to open the book and start your story. Your first question should display if the code is working properly.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>**Action**</th>
|
||||
<th>**Control**</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Move**</td>
|
||||
<td><kbd>W</kbd> <kbd>A</kbd> <kbd>S</kbd> <kbd>D</kbd> or arrow keys</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Rotate**</td>
|
||||
<td>Hold the right mouse button and look around.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Pan**</td>
|
||||
<td>Hold the middle mouse button to drag your camera around.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../assets/education/story-games/StoryExample.png" width="70%" />
|
||||
|
||||
1. Answer the question and click the **Submit** button.
|
||||
|
||||
<img src="../../../assets/education/story-games/wcc2018_annotatedStory.png" />
|
||||
|
||||
1. When you're done, return to the top-left corner of Studio, then click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../assets/education/general/stop-button.png" width="360" />
|
||||
|
||||
If your first sentence didn't show up, check out the following troubleshooting tips:
|
||||
|
||||
**Issue**: The book doesn't display your first question.
|
||||
|
||||
Check that the question is inside of quotation marks.
|
||||
|
||||
**Issue**: The story is combined.
|
||||
|
||||
Check the following:
|
||||
|
||||
- The first part of the story is inside of quotation marks.
|
||||
- The name of the variable holding the player answers matches exactly. Capitalization counts!
|
||||
- The name of the variable holding the player's answer is not inside of quotation marks.
|
||||
- The two strings are separated by `..`.
|
||||
|
||||
**Issue**: The book doesn't display the first sentence of your story.
|
||||
|
||||
Check the `storyMaker:Write()` function to make sure the **story** variable is in between the parentesis.
|
||||
|
||||
## Publish to Roblox
|
||||
|
||||
If you were to close Studio now, you would lose every edit you made to your experience. For this reason, it's important to publish your work to Roblox often to save your work and connect the experience to your account.
|
||||
|
||||
<Alert severity="info">
|
||||
It's recommended to publish to Roblox every ten minutes or after making a big change.
|
||||
</Alert>
|
||||
|
||||
1. In the top-left corner of your computer, click **File** ⟩ **Publish to Roblox** to open the publishing window.
|
||||
|
||||
1. In the **Publish Game** window,
|
||||
|
||||
1. In the **Name** field, provide a name for your experience.
|
||||
1. <Chip label="OPTIONAL" size="small" variant="outlined" /> In the **Description** field, provide a summary of what a player can expect from the experience.
|
||||
1. In the **Devices** section, enable every device you want players to use to access your experience.
|
||||
1. At the bottom-right of the window, click the **Create** button.
|
||||
|
||||
Now that your experience is published and connected to your account, you can edit it from any computer!
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle>Saving after publishing</AlertTitle>
|
||||
Next time you want to save your work, just go to **File** ⟩ **Publish to Roblox** or use the hotkey combo (<kbd>Alt</kbd><kbd>P</kbd>/<kbd>⌥</kbd><kbd>P</kbd>).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
title: Work with variables
|
||||
description: Explains how to work with variables to create a story experience.
|
||||
next: /tutorials/curriculums/coding/test-and-save
|
||||
prev: /tutorials/curriculums/coding/get-started
|
||||
---
|
||||
|
||||
In coding, **variables** are placeholder words for different types of information in your experience, such as player names, a team's score, or words for a story game. In this chapter, let's explore how to open the main script for the core gameplay of the experience and add code to ask players questions and use their answers for your story.
|
||||
|
||||
## Open script
|
||||
|
||||
Roblox creators write their code inside of **scripts** that tell Studio how and when to add custom behavior to experiences, such as triggering different types of events, saving player information, or spawning enemies. Roblox scripts use the [Luau](https://luau.org) programming language, which you can learn more about later in the [Coding fundamentals](../../fundamentals/coding-1/) series.
|
||||
|
||||
The Story Game template uses a single script named **StoryManager** to control the core gameplay of the experience. This script already contains some code that's necessary for showing the full story to the player, but you will add more to display your interactive story.
|
||||
|
||||
To open the **StoryManager** script:
|
||||
|
||||
1. In the **Explorer** window, click the arrow next to **StarterGui** to see everything it contains. **GameGui** displays.
|
||||
1. Click the arrow next to **GameGui** to see everything it contains. The **StoryManager** script displays at the top along with a few other things that aren't important for this lesson.
|
||||
|
||||
<img src="../../../assets/education/story-games/StoryManager.png" width="40%" />
|
||||
|
||||
1. Double-click the **StoryManager** script. The script's code displays.
|
||||
|
||||
```lua
|
||||
-- GLOBAL VARIABLES
|
||||
local storyMaker = require(script:WaitForChild("StoryMaker"))
|
||||
|
||||
-- Code controlling the game
|
||||
local playing = true
|
||||
|
||||
while playing do
|
||||
storyMaker:Reset()
|
||||
|
||||
-- Code story between the dashes
|
||||
-- =============================================
|
||||
|
||||
|
||||
-- =============================================
|
||||
|
||||
-- Add the story variable between the parenthesis below
|
||||
storyMaker:Write()
|
||||
|
||||
-- Play again?
|
||||
playing = storyMaker:PlayAgain()
|
||||
end
|
||||
```
|
||||
|
||||
Notice how a large portion of the code uses the `--` symbol, which is called a **comment**. Coders use comments to leave notes, and they don't change the way the script runs. For this lesson, you are going to create your code between the dashed line comments so that you can focus on creating your interactive story in one spot.
|
||||
|
||||
## Ask players questions
|
||||
|
||||
As a best practice, coders create variables with names that describe the type of information they store. For example, if you were working on a game where players are split into teams:
|
||||
|
||||
- The `team` variable stores information about the player's **team**.
|
||||
- The `teamColor` variable stores information about the player's **team color**.
|
||||
- The `teamScore` variable stores information for the player's **team score**.
|
||||
|
||||
Variables can store all sorts of information, such as small numbers, true or false answers, and strings. String type variables are special because they can store whole sentences, including the questions that you want to ask your players.
|
||||
|
||||
Using your placeholder words from the previous chapter, you can create string type variables with the same placeholder names that describe what type of word that you are going to ask the player for as they play your story game.
|
||||
|
||||
To create variables for your player questions:
|
||||
|
||||
1. Click below the dashed line comments and create a local variable for the first word you want players to replace with their own answer. For example:
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
1. Set the variable using the `=` symbol to the provided `storyMaker:GetInput()` method that displays your questions in the giant book. You must type this method exactly as it is with its specific capitalization in order for the code to work. For example:
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput()
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
1. Create a string in between the parenthesis with the question you want to ask your players. Remember, strings are always in quotation marks!
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
## Use their answers
|
||||
|
||||
Now that you have a question that players can answer, it's time to code your story and add behavior that replaces your placeholder word with their answer.
|
||||
|
||||
To create variables for your story and to use player answers:
|
||||
|
||||
1. Under your question, create a new variable named story. Remember, variables are lowercase!
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
1. Set your string type variable to the first sentence of your story before your first placeholder, leaving a space after the last word and before the quotation mark. If your placeholder word happens to be in the middle of a sentence, you can add the rest of the sentence in a later chapter of the tutorial.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard ")
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
1. Combine the first string of the story with the player's answer using `..`. Combining things together is called **concatenation**.
|
||||
1. On the same line as the **story** variable, add two periods.
|
||||
1. Add a space, then type the name of the variable that stores the player's first answer.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1)
|
||||
-- =============================================
|
||||
```
|
||||
|
||||
1. Show this first part of the story to players by calling the story variable from the provided `storyMaker:Write()` method that writes your story in the giant book.
|
||||
|
||||
```lua
|
||||
-- =============================================
|
||||
local name1 = storyMaker:GetInput("What is your favorite name?")
|
||||
|
||||
local story = ("In a tree on a hill lives the great wizard " .. name1)
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
storyMaker:Write(story)
|
||||
```
|
||||
|
||||
Now's a good point to stop momentarily, playtest the experience in the next chapter, and make sure the story is working!
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: Create a project
|
||||
description: This part of the tutorial gets you started with creating a project.
|
||||
prev: /tutorials/curriculums/core/
|
||||
next: /tutorials/curriculums/core/building/create-an-environment-with-terrain
|
||||
---
|
||||
|
||||
In order to start the process of recreating the **Island Jump** [3D platformer experience](https://www.roblox.com/games/14238807008), you must create a project within Roblox Studio. A project is a collection of places, assets, settings and other resources that together represent an experience. You can either start a project from scratch, or you can start with one of Studio's templates to provide a basis for your environment and gameplay mechanics, such as:
|
||||
|
||||
- **Baseplate** template — Contains a baseplate and `Class.SpawnLocation` object.
|
||||
- **Modern City** template — Contains a sample high-quality city and modular asset kit.
|
||||
- **Laser Tag** template — Contains assets and scripts for a first-person shooter experience.
|
||||
|
||||
To create a project with the **Baseplate** template:
|
||||
|
||||
1. Open Roblox Studio.
|
||||
1. In the upper-left corner, click the **New Experience** button. Studio opens a new experience.
|
||||
1. In the **Explorer** window on the right side of the window, expand the **Workspace** branch and then select the **Baseplate** object.
|
||||
|
||||
<img src="../../../../assets/studio/explorer/Workspace-Baseplate.png" width="320" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the **Explorer** window, toggle it on from Studio's **Window** menu.
|
||||
</Alert>
|
||||
|
||||
1. Press <kbd>Delete</kbd>. The viewport displays an empty world with a spawn location in the middle.
|
||||
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Baseplate-Deleted.jpg" alt="A viewport view of the baseplate template without the baseplate. The spawn location is highlighted in the middle of the view." width="81%" />
|
||||
|
||||
In the next section of the tutorial, you will learn how to use the **Terrain Editor** to create an island environment around the spawn location.
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
title: Create an environment with terrain
|
||||
description: Explains how to create the island where players spawn using Studio's terrain tools.
|
||||
next: /tutorials/curriculums/core/building/greybox-a-playable-area
|
||||
prev: /tutorials/curriculums/core/building/create-a-project
|
||||
---
|
||||
|
||||
<iframe width="800" height="495" src="https://www.youtube-nocookie.com/embed/_ZW5g7er4Zc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Creating an environment with terrain** allows you to generate and customize organic materials within the 3D space that look and behave like natural materials from the real world. Using tools within the [Terrain Editor](../../../../studio/terrain-editor.md), this section of the tutorial teaches you how to generate and sculpt terrain for a small island where players spawn and navigate to the first few platforms of your experience.
|
||||
|
||||
Before you begin, it's important to note that sculpting terrain is an art form, and it's difficult to exactly recreate brush strokes and subtle material edits. As long as your terrain meets the needs of your own experience, it's normal and expected for your environment to look and feel different from the sample [Island Jump](https://www.roblox.com/games/14239096301/) experience.
|
||||
|
||||
## Create the island
|
||||
|
||||
The first step in creating your environment is to create a small island that players will spawn to when they start the experience, and when their health reaches zero after they fall off a platform. Using the **Draw** tool, you can click and drag anywhere within the viewport to generate a large sphere of terrain to start the island, which you can later shape and flatten for a wide surface area.
|
||||
|
||||
To create the island:
|
||||
|
||||
1. Navigate to the toolbar's **Home** tab and click the **Terrain** button.
|
||||
1. In the **Terrain Editor** window, click the **Edit** tab, then the **Draw** button.
|
||||
|
||||
<img src="../../../../assets/studio/terrain-editor/Edit-Tab-Draw.png" alt="Studio's Terrain Editor window with both the Edit tab and Draw button highlighted." width="360" />
|
||||
|
||||
1. In the **Brush Settings** and **Material Settings** sections, keep all default setting except for the following:
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Editor-Draw-Settings.png" alt="Studio's Draw tool settings for this step in the task. The Brush Mode's Add button is highlighted." width="360" />
|
||||
</figure>
|
||||
<Grid item>
|
||||
- Set **Brush Size** to **32**.
|
||||
- Set **Source Material** to **Sand**.
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
1. In the viewport, click near the spawn location in the viewport. A sphere of sand material displays.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Adding-First-Sphere.mp4" />
|
||||
</video>
|
||||
|
||||
<Alert severity="info">
|
||||
If you cannot see the sphere, try zooming your camera out until your spawn location is smaller.
|
||||
</Alert>
|
||||
|
||||
## Shape the island
|
||||
|
||||
If you keep the island in its current shape, players would have a difficult time navigating without falling off the island. Using the **Flatten** tool, you can click and drag on the sphere to evenly level the terrain on a fixed plane, and provide players a relatively flat surface as they start your experience. While this shape initially looks unnatural, you can also use the **Sculpt** tool around the edges of the island to ensure it looks organic and realistic.
|
||||
|
||||
To shape the island:
|
||||
|
||||
1. In the **Terrain Editor** window, click the **Flatten** button.
|
||||
|
||||
<img src="../../../../assets/studio/terrain-editor/Edit-Tab-Flatten.png" alt="Studio's Terrain Editor window with both the Edit tab and Flatten button highlighted." width="360" />
|
||||
|
||||
1. In the **Brush Settings** section, keep all default setting except for the following:
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Editor-Flatten-Settings.png" alt="Studio's Flatten tool settings for this step in the task." width="360" />
|
||||
</figure>
|
||||
<Grid item>
|
||||
- Set **Brush Size** to `18`.
|
||||
- Set **Flatten Plane** to **Fixed**. New settings display.
|
||||
- Set **Fixed Y Plane** to `0`.
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
1. In the viewport, click and drag your mouse around the sphere until it's completely flat on top.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Flattening-Sphere.mp4" />
|
||||
</video>
|
||||
|
||||
1. Navigate back to the **Terrain Editor** window, then click the **Sculpt** button.
|
||||
|
||||
<img src="../../../../assets/studio/terrain-editor/Edit-Tab-Sculpt.png" alt="Studio's Terrain Editor window with both the Edit tab and Sculpt button highlighted." width="360" />
|
||||
|
||||
1. In the **Brush Settings** and **Material Settings** sections, set **Source Material** to **Sand**, otherwise keep all default settings.
|
||||
|
||||
<img src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Editor-Sculpt-Settings.png" alt="Studio's Sculpt tool settings for this step in the task. The Brush Mode's Add button highlighted." width="360" />
|
||||
|
||||
1. In the viewport, click and drag your mouse around the island's edges and below the water line to make the island look more natural.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Sculpting-Edges.mp4" />
|
||||
</video>
|
||||
|
||||
<Alert severity="info">
|
||||
The **Draw** tool adds or subtracts material based on the brush's position, whereas the **Sculpt** tool only
|
||||
grows or erodes existing terrain.
|
||||
</Alert>
|
||||
|
||||
## Create water
|
||||
|
||||
While there are multiple ways to generate a large body of water, the following instructions utilize the
|
||||
Terrain Editor's **Fill** tool. This method allows you to replace any material in a specific region with another
|
||||
material, including air itself.
|
||||
|
||||
To create the water around the island:
|
||||
|
||||
1. In the **Terrain Editor** window, click the **Fill** button.
|
||||
|
||||
<img src="../../../../assets/studio/terrain-editor/Edit-Tab-Fill.png" alt="Studio's Terrain Editor window with both the Edit tab and Fill button highlighted." width="360" />
|
||||
|
||||
1. In the **Selection Settings** section:
|
||||
1. Set **Size** to `1800, 5, 1800` to ensure the water fills toward the horizon of your experience.
|
||||
1. Set **Position** to `0, -15, 0` to ensure the water fills below the top of the island.
|
||||
1. In the **Material Settings** section, configure the tool with the following settings:
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Editor-Fill-Settings.png" alt="Studio's Fill tool material settings for this step in the task. The Material Mode's Replace button, source material, and target material are highlighted." width="360" />
|
||||
</figure>
|
||||
<Grid item>
|
||||
- Set **Fill Mode** to **Replace**.
|
||||
- Set **Source Material** to **Air**.
|
||||
- Set **Target Material** to **Water**.
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
1. Click the **Replace** button. Water generates around the island.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Filling-Water.mp4" />
|
||||
</video>
|
||||
|
||||
## Apply materials
|
||||
|
||||
Now that you have the basis of your island, you can customize its appearance with different materials. Using the **Paint** tool,
|
||||
you can click and drag on your terrain to apply both a grassy surface and blades of grass to the middle of the island's surface.
|
||||
|
||||
To apply materials to the island:
|
||||
|
||||
1. In the **Terrain Editor**, click the **Paint** button.
|
||||
|
||||
<img src="../../../../assets/studio/terrain-editor/Edit-Tab-Paint.png" alt="Studio's Terrain Editor window with both the Edit tab and Paint button highlighted." width="360" />
|
||||
|
||||
1. In the **Brush Settings** and **Material Settings** sections, keep all default setting except for the following:
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Editor-Paint-Settings.png" alt="Studio's Paint tool settings for this step in the task. The material is highlighted." width="360" />
|
||||
</figure>
|
||||
<Grid item>
|
||||
- Set **Material Mode** to **Paint**.
|
||||
- Set **Source Material** to **Leafy Grass**.
|
||||
</Grid>
|
||||
</GridContainer>
|
||||
|
||||
1. In the viewport, click and drag around the middle of the island to apply the leafy grass material.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/Terrain-Painting-Grass.mp4" />
|
||||
</video>
|
||||
|
||||
1. Navigate back to the **Terrain Editor** window, then in the **Brush Settings** and **Material Settings** sections,
|
||||
1. Set **Brush Size** to `3`.
|
||||
1. Set **Material** to **Grass**.
|
||||
1. In the viewport, drag along the edges of the island to apply blades of grass, leaving space in the middle of the island for
|
||||
the spawn location and the initial platforms.
|
||||
1. In the **Explorer** window, select the **SpawnLocation** object.
|
||||
1. From the toolbar, select the **Move** tool.
|
||||
1. In the viewport, move the spawn location toward the edge of the island to make space for the first platform. The sample [Island Jump - Building](https://www.roblox.com/games/14239096301/Island-Jump-Building) `.rbxl` file uses a position of `-127, -3, 9`.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/create-an-environment-with-terrain-spawn.mp4" />
|
||||
</video>
|
||||
|
||||
<Alert severity="info">
|
||||
You can create completely custom material appearance using [Material
|
||||
Overrides](/parts/materials#material-overrides), which allow you to provide
|
||||
custom texture assets to replace any default terrain texture.
|
||||
</Alert>
|
||||
|
||||
## Playtest
|
||||
|
||||
Once you are happy with your island's appearance, you can playtest your experience to check the scale of the island and how it feels in the context of your experience's 3D world.
|
||||
|
||||
To playtest your experience:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. Walk around the island and check out how it looks while playing.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-an-environment-with-terrain/create-an-environment-with-terrain-walking.mp4" />
|
||||
</video>
|
||||
|
||||
<Alert severity="warning">
|
||||
If your character faces a different direction than expected upon spawning, such as out toward the ocean instead of toward the center of the island, rotate the `Class.SpawnLocation` object and playtest again, repeating the process until the character spawns in the appropriate direction.
|
||||
</Alert>
|
||||
|
||||
1. When you're done, click the **Stop** button. Studio exits playtest mode.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Stop.png" width="800" alt="Stop button indicated in Studio's mezzanine." />
|
||||
|
||||
In the next section of the tutorial, you will learn how to create a playable area for players to follow in order to reach the highest platform.
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
---
|
||||
title: Create basic visual effects
|
||||
description: Explains how to create two different kinds of visual effect using particle emitters.
|
||||
prev: /tutorials/curriculums/core/scripting/script-an-upgrade-button
|
||||
next: /tutorials/curriculums/core/building/customize-global-lighting
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/Pk_UX4zMfeg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Creating basic special effects** for your experience adds dynamic movement to your environment,
|
||||
which results in your world feeling more alive and realistic. In addition, the visual interest
|
||||
and motion from special effects often catches players' attention, making them a useful device
|
||||
for leading players where you want them to go in your experience.
|
||||
|
||||
This section of the tutorial teaches you how to utilize **particle emitters**, a type of special
|
||||
effect that emits 2D images, or particles, in unique ways according to their parent object and
|
||||
how you configure their settings. Using examples from the sample [Island Jump - Final](https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample) experience, you will learn how to customize particle emitters to create both
|
||||
powerful and subtle effects, such as the glowing flare that draws players closer, and floating dust
|
||||
particles that add texture to the atmosphere.
|
||||
|
||||
## Create a flare
|
||||
|
||||
The first type of particle emitter the sample [Island Jump - Final](https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample) uses to add dynamic movement to the
|
||||
experience is a giant flare at the top of the tallest sea stack platform. As the rest of the environment
|
||||
is static, this effect becomes a focal point in the 3D space, which incentivizes players to progress through the environment
|
||||
so that they can reach the final platform of the experience.
|
||||
|
||||
To create a flare:
|
||||
|
||||
1. In the **Explorer** window, add a new folder into the **World** folder, then rename the new folder to **VFX**.
|
||||
1. Add a **block** part into the **VFX** folder, then position the part about 10 studs above your tallest sea stack platform. The sample **Island Jump - Final** experience positions this part above the **Level_7** platform with the following values:
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`20, 20, 20`</td>
|
||||
<td>`400, 331, 79`</td>
|
||||
<td>`0, 0, 0`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
1. Select this block part, then in the **Properties** window,
|
||||
1. Set **Name** to **VFX_Flare**.
|
||||
1. Set **Transparency** to `1` so the part is invisible.
|
||||
1. Enable **Anchored** so the physics system doesn't move the part when the experience starts.
|
||||
1. Add an attachment to this part.
|
||||
1. In the **Explorer** window, hover over the block part and click the **⊕** button. A contextual menu displays.
|
||||
1. From the contextual menu, insert an **Attachment**. An attachment displays in the center of the part in the direction of the positive Y axis.
|
||||
1. Add a particle emitter to this attachment, and rename the particle emitter to **Emitter_Flare**. The particle emitter immediately emits particles in the direction of the attachment.
|
||||
|
||||
<video controls src="../../../../assets/tutorials/core-building-and-scripting/Flare-1.mp4" width="80%"></video>
|
||||
|
||||
### Configure the flare
|
||||
|
||||
Now that you have a particle emitter in your experience, you can customize its properties so that it emits a glowing flare that
|
||||
faces players when they start the experience. To learn more about how each property affects the resulting visual effect, see `Class.ParticleEmitter|ParticleEmitter` and [Customizing particles](../../../../effects/particle-emitters.md#customize-particles).
|
||||
|
||||
#### Particle image
|
||||
|
||||
Each particle displays an image set by the `Class.ParticleEmitter.Texture|Texture` property. To use your own image, you need to upload the image to Roblox and get an asset ID. See [Assets](../../../../projects/assets/index.md) for more on this process and how to do it yourself.
|
||||
|
||||
You can use Roblox's pre-made flare image for the `Class.ParticleEmitter.Texture|Texture` of the particle emitter. To use Roblox's pre-made asset:
|
||||
|
||||
1. In the **Explorer** window, select **Emitter_Flare**.
|
||||
1. In the **Properties** window, set **Texture** to `rbxassetid://8983307836`.
|
||||
|
||||
#### Basic properties
|
||||
|
||||
`Class.ParticleEmitter.Rate` determines the amount of particles emitted per second. A rate of `5` means that a particle will emit every `1/5 = 0.2` seconds. Higher values of `Class.ParticleEmitter.ZOffset` means the particles render in front of other objects, while negative values mean they render behind other objects.
|
||||
|
||||
`Class.ParticleEmitter.LightEmission` determines the blending of the texture's colors with the colors behind them. At `0` the textures blend normally; at `1` they blend additively so that when particles overlap, their color multiplies to be brighter. The texture provided is designed to be used with this property set to 1.
|
||||
|
||||
Properties such as `Class.ParticleEmitter.Lifetime` require a minimum and maximum value, where Roblox chooses a random duration in seconds between the minimum and maximum each time a particle. In this case, the particles should all last 10 seconds with no variation, so both values are `10`.
|
||||
|
||||
To configure the basic properties of the particle emitter:
|
||||
|
||||
1. In the **Explorer** window, select **Emitter_Flare**.
|
||||
1. In the **Properties** window,
|
||||
|
||||
1. Set **Color** to `127, 84, 59`, or to a color you prefer for the flare.
|
||||
1. Set **LightEmission** to `1` to use additive blending.
|
||||
1. Set **ZOffset** to `1` to ensure it appears as expected in relation to the camera.
|
||||
1. Set **Lifetime** to `10, 10`.
|
||||
1. Set **Rate** to `0.45`.
|
||||
1. Set **RotSpeed** to `20` to rotate each particle 20 degrees per second.
|
||||
1. Set **Speed** to `0` to prevent the particle from moving.
|
||||
|
||||
#### Lifetime and NumberSequence values
|
||||
|
||||
Some properties such as `Class.ParticleEmitter.Size` and `Class.ParticleEmitter.Transparency` use a `Datatype.NumberSequence` to automate changes in the value of the property for a particle throughout its `Class.ParticleEmitter.Lifetime|Lifetime`. For example, the sequences for the flare's `Class.ParticleEmitter.Size|Size` and `Class.ParticleEmitter.Transparency|Transparency` create a pulsing effect each time a particle emits.
|
||||
|
||||
To configure the sequences for `Class.ParticleEmitter.Size` and `Class.ParticleEmitter.Transparency`:
|
||||
|
||||
1. In the **Explorer** window, select **Emitter_Flare**.
|
||||
1. In the **Properties** window, click the **…** next to the value for **Size** to open its `Datatype.NumberSequence`.
|
||||
1. Add points to the sequence by clicking on it, and move them until the window resembles the following example:
|
||||
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Flare-NumberSequence-Size.png" alt="A number sequence window where the size starts at 0 and grows slowly in the beginning of its lifetime, then quickly grows to a size of 10 and remains 10 for the duration of the lifetime." width="888" />
|
||||
<figcaption>The Y axis represents each particle's size and the X axis represents each particle's lifetime. The size starts at 0 and grows slowly in the beginning of its lifetime, then quickly grows to a size of 10 and remains 10 for the duration of the lifetime.</figcaption>
|
||||
</figure>
|
||||
|
||||
1. Click the **…** next to the value for **Transparency** to open its sequence.
|
||||
1. Add points to the sequence by clicking on it, and move them until the window resembles the following example:
|
||||
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Flare-NumberSequence-Transparency.png" alt="A number sequence window where the particle is visible (equal or close to 0) for the majority of its lifetime. As the particle approaches the end of its lifetime, its transparency value bounces up and down at different values, settling at 1 at the very end." width="888" />
|
||||
<figcaption>The particle is visible (equal or close to 0) for the majority of its lifetime. As the particle approaches the end of its lifetime, its transparency value bounces up and down at different values, settling at 1 at the very end.</figcaption>
|
||||
</figure>
|
||||
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Final-Flare.jpg" alt="The final version of the flare against a bright blue sky." width="80%" />
|
||||
|
||||
### Add a PointLight
|
||||
|
||||
To ensure the flare stands out more, you can put a light into it. There are three different light objects you can use:
|
||||
|
||||
- `Class.PointLight` emits light spherically from a single point.
|
||||
- `Class.SpotLight` emits light in the shape of a cone in a given direction.
|
||||
- `Class.SurfaceLight` emits light from one face of a `Class.BasePart`.
|
||||
|
||||
A `Class.PointLight` is best for this to emit light spherically from the part at the position of the particle effect.
|
||||
|
||||
To create a light source in the part:
|
||||
|
||||
1. Add a **PointLight** to **VFX_Flare**.
|
||||
1. Select the **PointLight** object, then in the **Properties** window,
|
||||
1. Set **Brightness** to `2` to make the light brighter.
|
||||
1. Set **Range** to `36` to increase the light's range.
|
||||
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Flare-With-PointLight.jpg" alt="The final version of the flare hovering over a gray cylinder sea stack. The flare emits a gentle glow over the sea stack." width="80%" />
|
||||
|
||||
## Create the dust particles
|
||||
|
||||
The second type of particle emitter the sample [Island Jump - Final](https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample) uses to add dynamic movement to the experience is one that dust particles throughout the atmosphere. These particles surround the player, adding a sense of texture and depth to the air itself.
|
||||
|
||||
To create dust particles:
|
||||
|
||||
1. Insert a **block** part into the **VFX** folder, then scale it to encompass the entire playable area. The sample **Island Jump - Final** experience positions and scales this part with the following values:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`645, 355, 275`</td>
|
||||
<td>`198, 168, 26`</td>
|
||||
<td>`0, 0, 0`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Select this block part, then in the **Properties** window,
|
||||
1. Set **Name** to **VFX_DustMotes**.
|
||||
1. Set **Transparency** to `1` so the part is invisible.
|
||||
1. Disable **CanCollide** so players don't collide with the part as they move through the playable area.
|
||||
1. Enable **Anchored** so the physics system doesn't move the part when the experience starts.
|
||||
|
||||
1. Add a particle emitter to this part, then rename the particle emitter to **Emitter_DustMotes**. The particle emitter immediately emits particles within the part's area.
|
||||
|
||||
<video controls src="../../../../assets/tutorials/core-building-and-scripting/DustParticles-1.mp4" width="80%"></video>
|
||||
|
||||
### Configure the dust particles
|
||||
|
||||
The dust particle emitter requires some new properties to change. `Class.ParticleEmitter.Acceleration` determines how a particle's `Class.ParticleEmitter.Speed` changes throughout its lifetime. Acceleration is often used to apply a gravity effect to particles with a negative `Y` value.
|
||||
|
||||
`Class.ParticleEmitter.Rotation` defines the range of rotations in degrees for emitted particles, with positive values corresponding with the clockwise direction. To add some randomness to the rotation of each dust mote, you can create a range of angles to choose from.
|
||||
|
||||
For each point in a `Datatype.NumberSequence`, you can set an _envelope_ using the number input at the bottom of the window. An envelope sets the range from which Studio picks a random value higher or lower than the point's value each time a particle emits. The size of the envelope determines the range of the random selection. The sequence for `Class.ParticleEmitter.Transparency` includes an envelope so that each particle's visibility is unpredictable.
|
||||
|
||||
Here are the values for all other previously explained properties. Refer back to [Configure the flare](#configure-the-flare) for these explanations.
|
||||
|
||||
1. In the **Explorer** window, select **Emitter_DustMotes**.
|
||||
1. In the **Properties** window,
|
||||
|
||||
1. Set **Color** to `192, 241, 255`.
|
||||
1. Set **Size** to the following `Datatype.NumberSequence`:
|
||||
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/DustMotes-NumberSequence-Size.png" alt="A number sequence window where the size rises to 0.25 shortly after creation, then fades down gradually to 0." width="888" />
|
||||
<figcaption>The size rises to 0.25 shortly after creation, then fades down gradually to 0</figcaption>
|
||||
</figure>
|
||||
|
||||
1. Set **Texture** to **rbxassetid://14302399641**.
|
||||
1. Set **Transparency** to the following `Datatype.NumberSequence`:
|
||||
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/DustMotes-NumberSequence-Transparency.png" alt="A number sequence window where the particle begins fully transparent, becomes randomly more opaque with an envelope of 0.1, then slowly fades out." width="888" />
|
||||
<figcaption>Begins fully transparent, becomes randomly more opaque (envelope of 0.1), then slowly fades out</figcaption>
|
||||
</figure>
|
||||
|
||||
1. Set **ZOffset** to `-5` so that they appear behind players and other objects.
|
||||
1. Set **Lifetime** `1, 10`.
|
||||
1. Set **Rate** to `50000`. This is a fast rate, but because the volume of the particle emitter's parent part is so large, it appears sparse.
|
||||
1. Set **Rotation** to `-45, 45`.
|
||||
1. Set **RotSpeed** to `-60`.
|
||||
1. Set **Speed** to `1, 5`.
|
||||
1. Set **Acceleration** to `1, -1, 1` to make the particles gently float upwards.
|
||||
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Dust-Motes-Sky.jpg" alt="The final version of the dust particles against a bright blue sky." />
|
||||
<figcaption>Faint dust particles in the air looking up at the sky from the platforms</figcaption>
|
||||
</figure>
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
title: Customize global lighting
|
||||
description: Explains how to use global lighting settings to refine the look and feel of your experience.
|
||||
prev: /tutorials/curriculums/core/building/create-basic-visual-effects
|
||||
next: /tutorials/curriculums/core/building/apply-polished-assets
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/VtsWu2G0ZiM?si=TZDy2jahPoWpCBnr" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
**Global lighting** is the luminescence from either the sun or moon in your environment. By customizing a couple of key default properties in the `Class.Lighting` service and its default child objects in Studio, you can drastically change how global lighting looks and feels in your experience, as well as how the light interacts with any other object you place in the 3D space.
|
||||
|
||||
Using only a few modifications to Studio's default lighting settings, this section of the tutorial teaches you how to customize your global lighting in order to change the sun's position and light color, make dramatic shadows, and thicken the atmosphere.
|
||||
|
||||
<Alert severity="info">
|
||||
There are many additional lighting objects you can customize to impact global lighting in your experience. For an overview on all lighting objects available in Studio, see [Lighting and effects](../../../../environment/index.md).
|
||||
</Alert>
|
||||
|
||||
## Set lighting properties
|
||||
|
||||
The `Class.Lighting` service contains five high-level categories of properties that you can adjust to customize the global lighting in your experience:
|
||||
|
||||
- **Color** - Configures hue within the environment.
|
||||
- **Intensity** - Configures the amount of light that hits the camera.
|
||||
- **Shadows** - Configures how shadows render within the environment.
|
||||
- **Appearance** - Properties that determine the lighting style and lighting/shading quality or view distance prioritization.
|
||||
- **Environment** - Configures environmental conditions, such as the time of day and geographic latitude.
|
||||
|
||||
The following instructions show you how to adjust properties within almost all of these categories to alter the colors of ambient and reflective lighting, make the edges of shadows sharper, utilize the most advanced lighting technology, and modify the position of the sun and your world to match the sample [Island Jump - Final](https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample) settings.
|
||||
|
||||
<Alert severity="info">
|
||||
For an overview on the additional properties within the `Class.Lighting` service, see [Global lighting](../../../../environment/lighting.md).
|
||||
</Alert>
|
||||
|
||||
### Adjust the color of light
|
||||
|
||||
The first step in matching the sample **Island Jump - Final** experience's `Class.Lighting` service properties is to adjust the color of both ambient and reflective lighting in your environment. There are two Lighting service properties that control the color of ambient lighting:
|
||||
|
||||
- `Class.Lighting.Ambient` controls the color of ambient lighting where there is cover from the sky, such as indoor spaces or under outdoor covering.
|
||||
- `Class.Lighting.OutdoorAmbient` controls the color of ambient lighting where the sky is visible.
|
||||
|
||||
In addition, the `Class.Lighting.ColorShift_Top` property controls the color of light that reflects from surfaces facing the sun or moon. By default, these three properties are set to produce a dark gray tone throughout your world, but to compliment the nautical sea stack environment of the final sample, you can adjust these properties to make both the ambient and reflective lighting have the subtle **blue-grey** tone of a traditional ocean palette.
|
||||
|
||||
<div><b>A comparison between the sample Island Jump experience's default and custom ambient lighting</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Default-Color.jpg" alt="The sample Island Jump experience with default ambient lighting visuals." />
|
||||
<figcaption>Default Properties</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-Color.jpg" alt="The sample Island Jump experience with custom ambient lighting visuals." />
|
||||
<figcaption>Custom Properties</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To adjust the color of ambient lighting in your environment:
|
||||
|
||||
1. In the **Explorer** window, select **Lighting**.
|
||||
2. In the **Properties** window,
|
||||
1. Set **Ambient** to `16, 16, 16`. The entire environment becomes subtly darker.
|
||||
1. Set **ColorShift_Top** to `196, 222, 255`. The hue that reflects from surfaces facing the sun lightens.
|
||||
1. Set **OutdoorAmbient** to `134, 158, 190`. All areas except for the tunnel display with a blue-grey hue.
|
||||
|
||||
### Enable realistic lighting
|
||||
|
||||
The second step in matching the sample **Island Jump - Final** experience's `Class.Lighting` service properties is to enable the most advanced lighting system. Studio begins every experience with `Enum.LightingStyle|Soft` lighting which renders a flatter look with softer lights and shadows. However, to enhance the environment and equip your local light sources to produce precise shadows and illumination, you must enable `Enum.LightingStyle|Realistic` lighting.
|
||||
|
||||
For example, the local lighting from the glowing flare with soft lighting doesn't produce a shadow at all, while the glowing flare with realistic lighting produces a subtle shadow near the circumference of the sea stack platform. This effect becomes more pronounced the more light sources you have within your environment.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/LightingStyle-Soft.jpg" alt="The sample Island Jump experience with Soft lighting style." />
|
||||
<figcaption>`Enum.LightingStyle|Soft` lighting style</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/LightingStyle-Realistic.jpg" alt="The sample Island Jump experience with Realistic lighting produces more prominent lighting from the flare."/>
|
||||
<figcaption>`Enum.LightingStyle|Realistic` lighting style</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To enable realistic lighting:
|
||||
|
||||
1. In the **Explorer** window, select **Lighting**.
|
||||
2. In the **Properties** window, click the **LightingStyle** dropdown, then select **Realistic**. The lighting system updates.
|
||||
|
||||
### Harden shadows
|
||||
|
||||
The third step in matching the sample **Island Jump - Final** experience's `Class.Lighting` service properties is to harden the shadows in your environment. This creates a more dramatic effect when players navigate between the outdoor and covered areas of your experience.
|
||||
|
||||
<div><b>A comparison between the sample Island Jump experience's default and custom shadows</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Default-Shadows.jpg" alt="The sample Island Jump experience with default shadow visuals that produce fuzzy shadows." />
|
||||
<figcaption>Default Shadows</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-Shadows.jpg" alt="The sample Island Jump experience with custom shadow visuals that produce sharp shadows."/>
|
||||
<figcaption>Custom Shadows</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To harden the shadows in your environment:
|
||||
|
||||
1. In the **Explorer** window, select **Lighting**.
|
||||
2. In the **Properties** window, set **ShadowSoftness** to `0`. Shadows produce hard edges.
|
||||
|
||||
### Change sun position
|
||||
|
||||
The final step in matching the sample **Island Jump - Final** experience's `Class.Lighting` service properties is to change the position of the sun in the sky. There are three properties that control the sun's position:
|
||||
|
||||
- `Class.Lighting.ClockTime` represents the current time of day from hour 0 through 24.
|
||||
- `Class.Lighting.TimeOfDay` represents the current time of day with a 24 hour string.
|
||||
- `Class.Lighting.GeographicLatitude` represents the geographic latitude in degrees.
|
||||
|
||||
<Alert severity="info">
|
||||
`Class.Lighting.ClockTime` and `Class.Lighting.TimeOfDay` are directly related, meaning that if you change one property, the other also changes, so you only need to customize one of these properties to change the time of day.
|
||||
</Alert>
|
||||
|
||||
The sun's default position is high in the sky, emulating around midday in the real world. However, to produce more pronounced shadows and directionality within the environment, you can move the sun to the right of the sea stack platforms.
|
||||
|
||||
<div><b>A comparison between the default and custom sun position</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Default-SunPosition.jpg" alt="The sample Island Jump experience with the default sun position high in the sky."/>
|
||||
<figcaption>Default sun position</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-SunPosition.jpg" alt="The sample Island Jump experience with a custom sun position approaching the horizon." />
|
||||
<figcaption>Custom sun position</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To change the sun position:
|
||||
|
||||
1. In the **Explorer** window, select **Lighting**.
|
||||
2. In the **Properties** window,
|
||||
1. Set **ClockTime** to `9`. The sun moves to the position it would be in the real world at 9:00 in the morning.
|
||||
1. Set **GeographicLatitude** to `78`. The world moves by 78 degrees, which also moves the sun to the right of your sea stack platforms.
|
||||
|
||||
## Atmosphere properties
|
||||
|
||||
The `Class.Lighting` service's child `Class.Atmosphere` object allows you to create realistic environmental lighting effects by scattering sunlight in unique ways based on properties that simulate air particles. These properties can be very useful in creating a thickness in the environment's air, giving the atmosphere a tangible sense of depth.
|
||||
|
||||
The following instructions show you how to adjust `Class.Atmosphere` properties to create a slightly thicker atmosphere to conceal the edge of the water and create more depth of field, just like the sample [Island Jump - Final](https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample) experience.
|
||||
|
||||
<Alert severity="info">
|
||||
For an overview on the additional properties within the `Class.Atmosphere`, see [Atmospheric Effects](../../../../environment/atmosphere.md).
|
||||
</Alert>
|
||||
|
||||
### Increase air particle density
|
||||
|
||||
The first step in matching the sample **Island Jump - Final** experience's `Class.Atmosphere` properties is to increase air particle density. The `Class.Atmosphere.Density` property controls how many particles exist in the air of your environment. When you increase this property, the additional amount of particles obstruct the players' view of objects in the background. This is particularly useful for hiding the boundaries of your water terrain.
|
||||
|
||||
<div><b>A comparison between the default and custom air particle density</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-SunPosition.jpg" alt="The sample Island Jump experience with the default air particle density that produces a clear background." />
|
||||
<figcaption>Default air particle density</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-AirParticleDensity.jpg" alt="The sample Island Jump experience with custom air particle density that produces a hazy background." />
|
||||
<figcaption>Custom air particle density</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To increase air particle density in your environment:
|
||||
|
||||
1. In the **Explorer** window, navigate to the **Lighting** service, then select its child **Atmosphere** object.
|
||||
2. In the **Properties** window, set **Density** to `0.375`. The air becomes thicker.
|
||||
|
||||
### Blend distant objects
|
||||
|
||||
The second step in matching the sample **Island Jump - Final** experience's `Class.Atmosphere` properties, as well as the final step in this section of the tutorial, is to blend distant objects on the horizon. The `Class.Atmosphere.Offset` property controls how light transmits between the camera and the sky background. When you increase this value, it creates a horizon silhouette; when you decrease this value, it blends distant objects into the sky for a seemingly endless and seamless open world.
|
||||
|
||||
While the sample experience could have set this property to zero to completely hide players' view of the horizon, the next section of the tutorial adds mountain objects near the boundaries of the experience that need to be visible. In addition, decreasing the default value without setting it to zero mimics a fog starting to roll in the distance, creating a more realistic environment.
|
||||
|
||||
<div><b>A comparison between the default and custom Offset values</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-AirParticleDensity.jpg" alt="The sample Island Jump experience with default Offset values that keep the background visible." />
|
||||
<figcaption>Default `Class.Atmosphere.Offset` properties</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/core-building-and-scripting/Custom-Offset.jpg" alt="The sample Island Jump experience with custom Offset values that hide the edges of the background."/>
|
||||
<figcaption>Custom `Class.Atmosphere.Offset` properties</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To blend distant objects in your environment:
|
||||
|
||||
1. In the **Explorer** window, navigate to the **Lighting** service, then select its child **Atmosphere** object.
|
||||
2. In the **Properties** window, set **Offset** to `0.17`. The air becomes thicker.
|
||||
|
||||
Now that your experience's global lighting is set to custom properties, in the next section of the tutorial, you will learn how to replace your greybox layout with high-quality polished assets.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Pre-Customization">
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Lighting-Pre-Customization.jpg" alt="The sample Island Jump experience's lighting visuals before the customization from this page." />
|
||||
</TabItem>
|
||||
<TabItem label="Customized Visual Effects">
|
||||
<img src="../../../../assets/tutorials/core-building-and-scripting/Lighting-Post-Customization.jpg" alt="The sample Island Jump experience's lighting visuals after the customization from this page." />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
---
|
||||
title: Greybox a playable area
|
||||
description: Explains how to use Studio's solid modeling tools to plan out the basic structure of your platforms.
|
||||
next: /tutorials/curriculums/core/scripting/script-game-behavior
|
||||
prev: /tutorials/curriculums/core/building/create-an-environment-with-terrain
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/yQJg1knsbh8?si=rmjdGMyHMFj5BdBM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Greyboxing an environment**, also known as massing out or blocking your environment, is the process
|
||||
of adding simple shapes to the 3D space to figure out how users will experience gameplay before investing
|
||||
time into scripting the gameplay or creating complex assets. This process can save you a lot of time in
|
||||
finding any issues in your layout, and it's much easier to make adjustments to basic parts than it is to
|
||||
adjust any high-quality meshes that you import into Studio.
|
||||
|
||||
Using basic parts and solid modeling operations, this section of the tutorial teaches you how to greybox
|
||||
the sea stack platforms that make up the playable area of the experience. Once you complete the environment,
|
||||
you will learn how to create the gameplay for the experience using Luau scripts.
|
||||
|
||||
## Plan the playable area
|
||||
|
||||
In the final version of the experience, players need to collect coins on the island
|
||||
and sea stack platforms in order to upgrade their jumping power and reach higher platforms. You will configure
|
||||
scripts to add this behavior in the next section of the tutorial, but it's important while you're starting to
|
||||
greybox your environment to plan for the difference in height between platforms. For example, the difference
|
||||
in height between platforms should slowly increase for each height level in order to encourage players to collect
|
||||
coins to progress through the level.
|
||||
|
||||
As a guide, the sample [Island Jump - Building](https://www.roblox.com/games/14239096301/Island-Jump-Building)
|
||||
`.rbxl` file includes seven different levels of height, with the first level sunk into the island to expose only
|
||||
a few studs of height. This allows players to only need to collect a few coins at the beginning of the experience
|
||||
in order to progress to the next platform. The subsequent height differences between each level then increase to
|
||||
8, 20, 35, 55, 81, and 110 studs, providing players a sense of progress as they make their way through your experience.
|
||||
|
||||
<Alert severity="info">
|
||||
|
||||
A stud is Studio's primary unit for length, and it's equivalent to 28cm.
|
||||
|
||||
</Alert>
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Platform-Levels.jpg" alt="A distant view of the sample island jump experience's greybox geometry. Each level of height difference between sea stacks is highlighted." />
|
||||
|
||||
<Alert severity="info">
|
||||
|
||||
If you want to expand your world beyond the simple design of the
|
||||
sample, ensure that each new height level requiring a jumping upgrade is
|
||||
at least 30 studs higher from the previous level.
|
||||
|
||||
</Alert>
|
||||
|
||||
## Add the platforms
|
||||
|
||||
Now that you have a plan for the difference in height between platforms, it's time to add in placeholder `Class.Part`
|
||||
objects to represent the sea stack platforms. **Parts** are Roblox's basic building blocks with properties you can modify
|
||||
to customize their physical appearance, such as their shape, size, and color.
|
||||
|
||||
While you can use almost any part shape to represent your sea stack platforms, it's recommended to greybox your environment with **cylinder** parts because they offer a flat surface for you to land on when you playtest jumping from platform to platform, and because they are similar in shape to the sea stack meshes you will use in the final section of the tutorial.
|
||||
|
||||
<div><b>A comparison between the sample Island Jump experience's greybox and final geometry</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/block-out-a-playable-area/Add-Platforms-Greybox.jpg" alt="The sample Island Jump experience's greybox geometry." />
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../../assets/tutorials/block-out-a-playable-area/Add-Platforms-Final.jpg" alt="The sample Island Jump experience's final geometry." />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Create an organization structure
|
||||
|
||||
Before you insert placeholder parts into the 3D space, it's important to create an organization structure for your assets in `Class.Workspace`. This process ensures that your workspace remains organized and easy to scan, especially as you venture into creating experiences with a significant amount of assets that you need to manage.
|
||||
|
||||
There are two types of containers you can use to group assets together: `Class.Folder` and `Class.Model` objects. **Folders** are useful for storing many objects of different types, and **models** are useful for storing geometric groupings of parts. The following instructions teach you how to use both container objects to store all assets for your 3D world.
|
||||
|
||||
To create an organization structure:
|
||||
|
||||
1. In the **Explorer** window, hover over the **Workspace** and click the **⊕** icon. A contextual menu displays.
|
||||
1. From the contextual menu, insert a **Folder** object. A folder object displays that you will use to contain all assets for the 3D world.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Explorer-Add-Folder.png" alt="Studio's Explorer window with both the Workspace's plus icon and Folder object highlighted." width="320" />
|
||||
|
||||
1. Rename the new folder `World`.
|
||||
|
||||
1. Right-click the folder object. A contextual menu displays.
|
||||
1. From the contextual menu, click **Rename**, and enter `World` for the
|
||||
folder's name.
|
||||
|
||||
<Alert severity="info">Ensure that the name is spelled correctly with the
|
||||
correct casing. The scripts that you write in Roblox are case-sensitive,
|
||||
and you'll write one later in this course to access this folder.</Alert>
|
||||
|
||||
1. Hover over the **World** folder and click the **⊕** icon.
|
||||
1. From the contextual menu, insert a **Model**.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Explorer-Add-Model.png" alt="Studio's Explorer window with both the World folder's plus icon and Model object highlighted." width="320" />
|
||||
|
||||
1. **Rename** the model to `Blockout_Parts`.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Explorer-Model-Renamed.png" alt="Studio's Explorer window with the new Blockout_Parts model highlighted under the World folder." width="320" />
|
||||
|
||||
### Insert parts
|
||||
|
||||
<Alert severity="info">
|
||||
The following instructions provide two different instructional paths: you can either insert parts into your own unique environment, or you can insert parts in a way that exactly recreates the greybox environment within the sample [Island Jump - Building](https://www.roblox.com/games/14239096301/Island-Jump-Building).
|
||||
</Alert>
|
||||
|
||||
Now that you have an organizational structure to contain your assets, you can now begin inserting parts into the 3D space to represent your sea stack platforms.
|
||||
|
||||
<Tabs>
|
||||
<TabItem key = "1" label="Create your own">
|
||||
|
||||
To insert a cylinder part for your first platform:
|
||||
|
||||
1. In the **Home** or **Model** tab's toolbar, click the small arrow next to the **Part** inserter tool to reveal the dropdown.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Part-Picker.png" alt="A close-up view of the part picker." width="800" />
|
||||
|
||||
1. From the dropdown menu, select **Cylinder**. A cylinder part displays in the viewport.
|
||||
1. In the **Explorer** window, click and drag the new **Part** to the **Blockout_Parts** model. The part becomes a child of the model.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/New-Part-As-Child-Of-Model.png" alt="Studio's Explorer window with the new Part highlighted under the Blockout_Parts model object." width="320" />
|
||||
|
||||
1. Use the **Move**, **Scale**, and **Rotate** tools to position, scale, and rotate your cylinder until it's a large, flat surface in the middle of your island. For more information on these tools, see [Transform parts](../../../../parts/index.md#transform-parts).
|
||||
|
||||
<Alert severity="success">
|
||||
To manipulate individual parts, select them in the Explorer. If you select a part in the viewport, it selects the entire model.
|
||||
</Alert>
|
||||
|
||||
<img src="../../../../assets/studio/general/Toolbar-Transform-Tools.png" alt="Transform tools indicated in Studio's toolbar." width="800" />
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/First-Platform.jpg" alt="A large cylinder object partially sticking out of an island surrounded by water." width="80%" />
|
||||
|
||||
1. Using the same process, add and configure at least seven more sea stack platforms with increasing levels of height into the **Blockout_Parts** model.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Final-Platforms.jpg" alt="Many large cylinder objects partially sticking out of an island surrounded by water, and the water itself." width="80%" />
|
||||
|
||||
1. In the **Explorer** window, select the **Blockout_Parts** model.
|
||||
1. In the **Home** or **Model** tab, toggle on **Anchor**. This ensures that the physics system doesn't move your parts when the experience starts.
|
||||
|
||||
</TabItem>
|
||||
<TabItem key = "2" label="Recreate the sample">
|
||||
|
||||
To exactly recreate the sea stack platforms within the sample [Island Jump - Building](https://www.roblox.com/games/14239096301/Island-Jump-Building) experience:
|
||||
|
||||
1. Navigate to Studio's **Home** tab toolbar.
|
||||
1. Click the **Part** dropdown arrow, then select **Cylinder**. A cylinder part displays in the viewport.
|
||||
1. In the **Explorer** window, click and drag the new **Part** to the **Blockout_Parts** model. The part becomes a child of the model.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/New-Part-As-Child-Of-Model.png" alt="Studio's Explorer window with the new Part highlighted under the Blockout_Parts model object." width="320" />
|
||||
|
||||
1. In the **Properties** window,
|
||||
|
||||
1. Set **Name** to **Level_1** for organization purposes.
|
||||
1. Set **Size** to `12, 131, 131` to create a large platform.
|
||||
1. Set **CFrame.Position** to `-23, -4, 9` so the top of the platform is only a short jump from the surface of the island.
|
||||
1. Set **CFrame.Orientation** to `0, 0, 90` so the flat surface of the cylinder faces toward the sky.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/First-Platform.jpg" alt="A large cylinder object partially sticking out of an island surrounded by water." width="80%" />
|
||||
|
||||
1. Using this process, add and configure seven more sea stack levels with the following values for the rest of the sea stack platforms:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Level_2</td>
|
||||
<td>`20, 81, 81`</td>
|
||||
<td>`-8, 0, 24`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_3a</td>
|
||||
<td>`40, 44, 44`</td>
|
||||
<td>`42, 10, 9`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_3b</td>
|
||||
<td>`40, 34, 34`</td>
|
||||
<td>`87, 10, 4`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_3c</td>
|
||||
<td>`40, 44, 44`</td>
|
||||
<td>`97, 10, 49`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_4a</td>
|
||||
<td>`75, 39, 39`</td>
|
||||
<td>`112, 27.5, 46.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_4b</td>
|
||||
<td>`75, 65, 65`</td>
|
||||
<td>`137, 27.5, 69`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_4c</td>
|
||||
<td>`75, 60, 60`</td>
|
||||
<td>`159.5, 27.5, 91.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_4d</td>
|
||||
<td>`75, 30, 30`</td>
|
||||
<td>`207, 27.5, 106.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_4e</td>
|
||||
<td>`75, 61, 61`</td>
|
||||
<td>`250, 27.5, 74`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_5a</td>
|
||||
<td>`130, 60, 60`</td>
|
||||
<td>`268, 55, 50`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_5b</td>
|
||||
<td>`130, 25, 25`</td>
|
||||
<td>`256, 55, 2.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_5c</td>
|
||||
<td>`130, 25, 25`</td>
|
||||
<td>`276, 55, -17.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_5d</td>
|
||||
<td>`130, 54, 54`</td>
|
||||
<td>`322, 55, -21`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_6a</td>
|
||||
<td>`211, 79, 79`</td>
|
||||
<td>`367, 94.5, -41`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_6b</td>
|
||||
<td>`211, 24, 24`</td>
|
||||
<td>`387, 94.5, 11.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_6c</td>
|
||||
<td>`211, 44, 44`</td>
|
||||
<td>`397, 94.5, 51.5`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Level_7</td>
|
||||
<td>`321, 61, 61`</td>
|
||||
<td>`407, 149.5, 79`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Final-Platforms.jpg" alt="Many large cylinder objects partially sticking out of an island and its surrounding water." width="80%" />
|
||||
|
||||
1. In the **Explorer** window, select the **Blockout_Parts** model.
|
||||
1. In Studio's **Home** tab toolbar, click the **Anchor** icon. This ensures that the physics system doesn't move your parts when the experience starts.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Align parts
|
||||
|
||||
<Alert severity="info">
|
||||
If you used the sample **Island Jump - Building** experience values when inserting your sea stack platforms, you can skip this step.
|
||||
</Alert>
|
||||
|
||||
As you add more sea stack placeholder parts off the island, it's easier to manage the height differences between levels if you use varying sizes for these parts instead of different positions. You can achieve this by **aligning** the base of each platform so that all vertical size differences are reflected in differing heights, and parts of the same size are on the same level.
|
||||
|
||||
The [Align Tool](../../../../studio/align-tool.md) aligns parts on either the minimum, center, or maximum edge according to a specific axis. For the purposes of this experience, you need to align the **bottom** edge in the **Y** axis so that all of the parts are partially submerged in the water.
|
||||
|
||||
To align parts:
|
||||
|
||||
1. In the **Explorer** window, select all of your platforms.
|
||||
1. In the toolbar's **Model** tab, click the **Align** button. The **Align Tool** window displays.
|
||||
|
||||
1. Set **Mode** to **Min**.
|
||||
2. Set **Align In** to **World**, **Y**.
|
||||
3. Keep **Relative To** on **Selection Bounds**.
|
||||
|
||||
1. Click the **Align** button. All parts align on the Y axis according to the part with the lowest Y `Class.Part.Position` value.
|
||||
<figure>
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Platforms-Aligned-Underwater.jpg" alt="An underwater view of many cylinders that are aligned along their bottom edge." />
|
||||
<figcaption>All platforms align their bottom edge</figcaption>
|
||||
</figure>
|
||||
|
||||
### Create a hollow tunnel
|
||||
|
||||
Aside from using parts as-is to block out your playable areas, you can also apply solid modeling operations to join parts in unique ways to form more complex shapes, such as a hollow tunnel within one of the sea stacks. This technique provides more visual interest and variation in how players interact with your environments.
|
||||
|
||||
There are four solid modeling tools:
|
||||
|
||||
- **Union** – Joins two or more parts together to form a single solid union.
|
||||
- **Intersect** – Intersects overlapping parts into a single solid intersection.
|
||||
- **Negate** – Negates parts, which is useful for making holes and indentations.
|
||||
- **Separate** – Separates the union or intersection back into its individual parts.
|
||||
|
||||
For the purposes of creating a hollow tunnel, you only need to use the **Union** and **Negate** tools. For a full breakdown of all of the tools, see [solid modeling](../../../../parts/solid-modeling.md).
|
||||
|
||||
To create a hollow tunnel:
|
||||
|
||||
1. Insert and position a **cylinder** part above one of your sea stack platforms. The sample [Island Jump - Building](https://www.roblox.com/games/14239096301/Island-Jump-Building) experience positions this part above the **Level_4b** platform with the following values:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Tunnel</td>
|
||||
<td>`24, 65, 69`</td>
|
||||
<td>`137, 77, 69`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Insert and position a **block** part to represent the hollow portion of your tunnel that's at least as tall as the cylinder part, and a suitable width for players to walk through. The sample **Island Jump - Building** experience positions this part within the previous cylinder with the following values:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hollow_Part</td>
|
||||
<td>`24.5, 72, 22`</td>
|
||||
<td>`134.5, 77, 71`</td>
|
||||
<td>`0, 135, 90`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/HollowTunnel-Start.jpg" alt="A close-up view of a gray block sticking out of a gray cylinder." width="740" />
|
||||
|
||||
1. In the **Explorer** window, select the block part.
|
||||
1. In Studio's **Model** tab toolbar, click the **Negate** button. The part turns translucent.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/HollowTunnel-Negate.jpg" alt="A close-up view of a partially translucent pink block sticking out of a gray cylinder." width="740" />
|
||||
|
||||
1. In the **Explorer** window, select both the negated part and the cylinder tunnel part.
|
||||
1. In Studio's **Model** tab toolbar, click the **Union** button. The negated part is cut out from the overlapping tunnel cylinder.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/HollowTunnel-Union.jpg" alt="A close-up view of a gray cylinder with an open tunnel through itself. The tunnel does not have a roof." width="740" />
|
||||
|
||||
1. Rename the new union to something that reflects its height level and position, such as **Level_4b_Union**.
|
||||
1. Duplicate the sea stack platform underneath your new union, and position it so that it's on top of the tunnel. The sample **Island Jump - Building** experience positions the duplicate **Level_4b** platform above the union with the following values:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>CFrame.Position</th>
|
||||
<th>CFrame.Orientation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Level_4b_Top</td>
|
||||
<td>`74, 65, 69`</td>
|
||||
<td>`137, 126, 69`</td>
|
||||
<td>`0, 0, 90`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/HollowTunnel-Final.jpg" alt="A close-up view of a tunnel through a cylinder." width="740" />
|
||||
|
||||
## Playtest the layout
|
||||
|
||||
After you finish greyboxing your playable areas, you must playtest the layout of your environment to ensure the experience is both fun and functional, and so that you can catch small issues before they turn into much larger projects the further you are in the development process. For example, your experience's gameplay needs players to steadily upgrade their jumping power according to the amount of coins they collect, so it's important to verify that players are able to jump between platforms in relation to the `Class.Humanoid.JumpPower` you expect players to have at varying platform height levels.
|
||||
|
||||
The following step-by-step instructions teach you how to playtest your experience with different `Class.Humanoid.JumpPower` values. As you playtest, ask yourself the following questions:
|
||||
|
||||
- Are players able to successfully jump to each platform?
|
||||
- Does the difference in height between platforms slowly increase for each height level in order to encourage players to progress?
|
||||
- What am I enjoying or getting frustrated about the layout or gameplay?
|
||||
|
||||
To playtest your experience:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. In the **Explorer** window, select the arrow next to your character model that displays your Roblox username. All of your character model's children objects display.
|
||||
1. Select **Humanoid**.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Explorer-Character-Humanoid.png" alt="Studio's Explorer window with the Humanoid object highlighted. The hierarchy of the Workspace to CharacterModel to Humanoid is also highlighted." width="320" />
|
||||
|
||||
1. In the **Properties** window, navigate to the **Jump Settings** section, then enable **UseJumpPower**. The **JumpPower** property displays with a default value of `50`.
|
||||
1. Set **JumpPower** to `0`. This ensures your character is unable to jump, emulating the same starting state for players after you script the gameplay.
|
||||
|
||||
<img src="../../../../assets/tutorials/block-out-a-playable-area/Humanoid-Jump-Settings.png" alt="Jump Settings with the JumpPower and UseJumpPower properties highlighted." width="320" />
|
||||
|
||||
1. As you reach new levels, set **JumpPower** to multiples of `30` to simulate jumping upgrades.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/block-out-a-playable-area/playable-area-walk.mp4" />
|
||||
</video>
|
||||
|
||||
In the next section of the tutorial, you will learn how to script the overall gameplay of the experience.
|
||||
113
.cursor/rules/roblox/en-us/tutorials/curriculums/core/index.md
Normal file
113
.cursor/rules/roblox/en-us/tutorials/curriculums/core/index.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
title: Core curriculum
|
||||
description: Learn the technical and creative essentials to building Roblox experiences.
|
||||
next: /tutorials/curriculums/core/building/create-a-project
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/zi0hIuPDyWc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "95%", height: "95%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
|
||||
The core curriculum helps you learn many essential Studio features across
|
||||
technical and creative disciplines.
|
||||
|
||||
You'll learn how to recreate a simple [3D platformer
|
||||
experience](https://www.roblox.com/games/14238807008) where players collect
|
||||
coins to trade for jump power. Players jump to navigate progressively taller
|
||||
platforms until they reach a flare on top of the highest platform.
|
||||
|
||||
This course is intended for readers who are familiar
|
||||
with general coding concepts but are new to Roblox. If you need help learning
|
||||
how to code, try the [absolute basics](../../first-experience/index.md) of working in Studio and
|
||||
[coding fundamentals](../../fundamentals/coding-1/coding-fundamentals.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
## Course contents
|
||||
|
||||
The core curriculum is split into three chapters, each with a
|
||||
corresponding place file. You can go through the steps for each chapter or
|
||||
inspect the place files if you want to see the end result of each chapter.
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Build a greybox</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
- [Create a project](./building/create-a-project.md) - Learn how to create a `.rbxl` file that represents your experience on the Roblox platform.
|
||||
- [Create an environment with terrain](./building/create-an-environment-with-terrain.md) - Learn how to create the island where players spawn using Studio's terrain tools.
|
||||
- [Greybox a playable area](./building/greybox-a-playable-area.md) - Learn how to use solid modeling tools to plan out the basic shape of your platforms.
|
||||
|
||||
<a href="https://www.roblox.com/games/14239096301/Island-Jump-Building">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the place file
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Script the gameplay</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
- [Create a coin collection mechanic](./scripting/script-game-behavior.md) - Learn how to track and store players' coin collecting.
|
||||
- [Record and display player data](./scripting/record-and-display-player-data.md) - Learn how to store, retrieve, and display individual player data visually through a leaderboard.
|
||||
- [Create player hazards](./scripting/create-player-hazards.md) - Learn how to create a hazard in the water by modifying player behavior and creating a player life cycle.
|
||||
- [Script an upgrade button](./scripting/script-an-upgrade-button.md) - Learn how to upgrade player jump power by communicating with the Roblox server and handling GUI interactions.
|
||||
|
||||
<a href="https://www.roblox.com/games/14239042199/Island-Jump-Scripting">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the place file
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Polish the experience</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
- [Create basic visual effects](./building/create-basic-visual-effects.md) - Learn how to create two different kinds of visual effect using particle emitters.
|
||||
- [Customize global lighting](./building/customize-global-lighting.md) - Learn how to use global lighting settings to refine the look and feel of your experience.
|
||||
- [Apply polished assets](./building/apply-polished-assets.md) - Learn how to finish your scene by replacing simple parts with complex, imported models.
|
||||
|
||||
<a href="https://www.roblox.com/games/14238807008/Island-Jump-Completed-Sample">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the place file
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: Create player hazards
|
||||
description: Explains how to create player hazards by modifying player behavior and creating a player life cycle.
|
||||
next: /tutorials/curriculums/core/scripting/script-an-upgrade-button
|
||||
prev: /tutorials/curriculums/core/scripting/record-and-display-player-data
|
||||
---
|
||||
|
||||
<iframe width="880" height="495"
|
||||
src="https://www.youtube-nocookie.com/embed/vlzZm3PQGfQ"
|
||||
title="YouTube video player" frameborder="0" allow="accelerometer; autoplay;
|
||||
clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
Hazard objects decrease the health of players when they touch them. As a simple
|
||||
starting point, this section of the tutorial teaches you how to create one large
|
||||
invisible part at the same level as the water in your experience, so that falling
|
||||
into the hazard changes the player's health to zero and respawns them back to the
|
||||
start of the experience.
|
||||
|
||||
## Create a basic water hazard
|
||||
|
||||
To create the basic water hazard:
|
||||
|
||||
1. In the **Explorer** window, add a new folder into the **World** folder, then rename it **Hazards**.
|
||||
Ensure that the name is spelled correctly with the correct casing, otherwise the code won't work.
|
||||
|
||||
1. In the **Hazards** folder, insert a **block** part and rename it **Hazard**.
|
||||
|
||||
<img src="../../../../assets/tutorials/create-player-hazards/New-Hazard-Part.png" alt="Studio's Explorer window with the Hazard block Part highlighted under the Hazards folder." width="320" />
|
||||
|
||||
1. Move and scale the part to cover the water line around the island and platforms. For example, the
|
||||
sample [Island Jump - Scripting](https://www.roblox.com/games/14239042199/Island-Jump-Scripting) experience
|
||||
sets **Size** to `825, 1, 576` and **CFrame.Position** to `174, -6.5, 38`.
|
||||
|
||||
<img src="../../../../assets/tutorials/create-player-hazards/Hazard-Part-On-Water.jpg" alt="A far out view of all of the cylinder sea stacks and the island. A large block part covers the water where a player could land if they fell from a sea stack." width="600" />
|
||||
|
||||
1. Select the part, then in the **Properties** window, configure the following properties so
|
||||
the hazard is invisible, and players can pass right through it:
|
||||
|
||||
- Set **Transparency** to `1`. This makes the hazard invisible, so that the
|
||||
actual water appears to be the hazard.
|
||||
- Disable **CanCollide**. This tells the engine that other parts can pass
|
||||
through the hazard uninterrupted, meaning players can fall through the hazard.
|
||||
- Enable **Anchored**. This tells the engine to never change the
|
||||
position of the hazard due to any physics-related simulation, meaning that
|
||||
players can touch the hazard without affecting its location.
|
||||
|
||||
1. Create a **Script** in **ServerScriptService**, then rename it to **HazardService**.
|
||||
1. Replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
local Players = game:GetService("Players")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local hazardsFolder = Workspace.World.Hazards
|
||||
local hazards = hazardsFolder:GetChildren()
|
||||
|
||||
local function onHazardTouched(otherPart)
|
||||
local character = otherPart.Parent
|
||||
local player = Players:GetPlayerFromCharacter(character)
|
||||
if player then
|
||||
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
|
||||
if humanoid then
|
||||
humanoid.Health = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, hazard in hazards do
|
||||
hazard.Touched:Connect(onHazardTouched)
|
||||
end
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
The **HazardService** has many similarities to **CoinService**. However,
|
||||
instead of collecting a coin, the player has their **health set to 0**
|
||||
when they touch a hazard.
|
||||
|
||||
Feel free to modify, add, or remove hazard objects in your experience to
|
||||
create unique obstacles. As long as they are contained in the **Hazards**
|
||||
folder, the code loop connects the event handler to all of your hazards.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Connect to the player lifecycle
|
||||
|
||||
The player lifecycle represents events that occur when players interact in your
|
||||
experience, such as joining, leaving, or respawning. You need to connect
|
||||
handlers to these events to appropriately execute logic for each major lifecycle
|
||||
event. In the **CoinService** script, copy and paste the following code at the
|
||||
bottom of the script:
|
||||
|
||||
```lua
|
||||
local function onPlayerAdded(player)
|
||||
-- Reset player coins to 0
|
||||
updatePlayerCoins(player, function(_)
|
||||
return 0
|
||||
end)
|
||||
|
||||
player.CharacterAdded:Connect(function(character)
|
||||
-- WaitForChild would stop the player loop, so below should be done in a separate thread
|
||||
task.spawn(function()
|
||||
-- When a player dies
|
||||
character:WaitForChild("Humanoid").Died:Connect(function()
|
||||
-- Reset player coins to 0
|
||||
updatePlayerCoins(player, function(_)
|
||||
return 0
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Initialize any players added before connecting to PlayerAdded event
|
||||
for _, player in Players:GetPlayers() do
|
||||
onPlayerAdded(player)
|
||||
end
|
||||
|
||||
local function onPlayerRemoved(player)
|
||||
updatePlayerCoins(player, function(_)
|
||||
return nil
|
||||
end)
|
||||
end
|
||||
|
||||
Players.PlayerAdded:Connect(onPlayerAdded)
|
||||
Players.PlayerRemoving:Connect(onPlayerRemoved)
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
The code defines functions to reset coin counts during the appropriate
|
||||
lifecycle events:
|
||||
|
||||
- `Class.Players.PlayerAdded` fires when a player joins the experience, and sets
|
||||
the coin count to `0`.
|
||||
- `Class.Player.CharacterAdded` fires when a player's character model is added
|
||||
to the world. It occurs after `Class.Players.PlayerAdded|PlayerAdded` and
|
||||
whenever the player respawns.
|
||||
- `Class.Humanoid.Died` fires when a player dies, and sets
|
||||
the coin count to `0`. `Library.task.spawn()` creates
|
||||
a separate thread for handling this, so other aspects of the player life cycle
|
||||
can execute.
|
||||
- `Class.Player.PlayerRemoved` fires when a player leaves the experience to
|
||||
clean up player state.
|
||||
- This code contains a potential issue where players could collect coins before the `Players.PlayerAdded` event executes and then have their coin counts reset to zero. To mitigate this issue, consider solutions such as code scheduling or freezing the player's character until initialization finishes. However, these solutions involve more complex scripting concepts that are beyond the scope of this tutorial.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Playtest
|
||||
|
||||
It's time to see if the player hazard works as intended. When you touch the
|
||||
water, your character should die and lose their coins. To test your game:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. Move your character to collect some coins, then jump in the water. If your
|
||||
scripts are working correctly, your character dies, and the coin count on
|
||||
the leaderboard resets to `0`.
|
||||
|
||||
<video
|
||||
controls loop muted>
|
||||
<source src="../../../../assets/tutorials/create-player-hazards/player-hazards-example.mp4" />
|
||||
</video>
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
---
|
||||
title: Record and display player data
|
||||
description: Explains how to store and retrieve individual player data in an experience and display it visually through a leaderboard.
|
||||
next: /tutorials/curriculums/core/scripting/create-player-hazards
|
||||
prev: /tutorials/curriculums/core/scripting/script-game-behavior
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/bfWTFFFcLXY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
Now that you can detect when a player has collected a coin, this section of the tutorial
|
||||
teaches you how to count how many coins players have collected, and make that amount visible
|
||||
on a leaderboard.
|
||||
|
||||
## Create a module script to record coin collection
|
||||
|
||||
To handle the storage and management of each player's coin collection data,
|
||||
you need to create a `Class.ModuleScript` object to contain a data structure and
|
||||
functions that access coin collection data for every player. Module scripts are
|
||||
reusable code that other scripts can require. In this case, the
|
||||
`CoinService` requires this module script so that it can update coin collection data
|
||||
when players touch coins.
|
||||
|
||||
To create a module script:
|
||||
|
||||
1. In the **Explorer** window, hover over **ServerStorage** and click the
|
||||
**⊕** button. A contextual menu displays.
|
||||
1. From the contextual menu, select **ModuleScript**. A new module script displays under **ServerStorage**.
|
||||
You are placing a module script into **ServerStorage** because you want to manage the coin collection
|
||||
logic on the server.
|
||||
|
||||
<img src="../../../../assets/tutorials/record-and-display-player-data/Insert-ModuleScript.png" alt="Studio's Explorer window with both the ServerScriptService's plus icon and ModuleScript object highlighted." width="320" />
|
||||
|
||||
1. Rename the module script to **PlayerData**.
|
||||
|
||||
<img src="../../../../assets/tutorials/record-and-display-player-data/ModuleScript-Renamed-PlayerData.png" alt="Studio's Explorer window with the PlayerData script highlighted under ServerStorage." width="320" />
|
||||
|
||||
1. Replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
local PlayerData = {}
|
||||
PlayerData.COIN_KEY_NAME = "Coins"
|
||||
|
||||
local playerData = {
|
||||
--[[
|
||||
[userId: string] = {
|
||||
["Coins"] = coinAmount: number
|
||||
}
|
||||
]]
|
||||
}
|
||||
|
||||
local DEFAULT_PLAYER_DATA = {
|
||||
[PlayerData.COIN_KEY_NAME] = 0
|
||||
}
|
||||
|
||||
local function getData(player)
|
||||
local data = playerData[tostring(player.UserId)] or DEFAULT_PLAYER_DATA
|
||||
playerData[tostring(player.UserId)] = data
|
||||
return data
|
||||
end
|
||||
|
||||
function PlayerData.getValue(player, key)
|
||||
return getData(player)[key]
|
||||
end
|
||||
|
||||
function PlayerData.updateValue(player, key, updateFunction)
|
||||
local data = getData(player)
|
||||
local oldValue = data[key]
|
||||
local newValue = updateFunction(oldValue)
|
||||
|
||||
data[key] = newValue
|
||||
return newValue
|
||||
end
|
||||
|
||||
return PlayerData
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
The module script defines a `PlayerData` table that contains zero or many
|
||||
`playerData` tables, which represent coin collection data for a player. Every
|
||||
script that requires this module script receives the same copy of the
|
||||
`PlayerData` table, allowing multiple scripts to modify and share coin
|
||||
collection data.
|
||||
|
||||
<h4>Declare the data structures</h4>
|
||||
|
||||
The module script begins with a declaration of an empty table, `PlayerData`,
|
||||
which is returned at the end of the script. It also contains
|
||||
accessor methods to get and set values in the table.
|
||||
|
||||
The `playerData` table contains comments that describe the structure of the
|
||||
table, which makes the code easier to understand. In this case, a
|
||||
`playerData` table contains a `userId` and a corresponding field named
|
||||
`Coins` that represents the amount of collected coins for that player.
|
||||
|
||||
```lua
|
||||
local PlayerData = {}
|
||||
PlayerData.COIN_KEY_NAME = "Coins"
|
||||
|
||||
local playerData = {
|
||||
--[[
|
||||
[userId: string] = {
|
||||
["Coins"] = coinAmount: number
|
||||
}
|
||||
]]
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
return PlayerData
|
||||
```
|
||||
|
||||
<h4>Define a local data accessor</h4>
|
||||
|
||||
`getData()` is a local function that retrieves data for a specific
|
||||
`playerData` table. If a player hasn't collected a coin, it returns a
|
||||
`DEFAULT_PLAYER_DATA` table to ensure that every player has some data
|
||||
associated with them. A common convention is to create simple,
|
||||
public-facing functions that offload logic to locally-scoped functions that
|
||||
do the heavy lifting.
|
||||
|
||||
```lua
|
||||
local DEFAULT_PLAYER_DATA = {
|
||||
[PlayerData.COIN_KEY_NAME] = 0
|
||||
}
|
||||
|
||||
local function getData(player)
|
||||
local data = playerData[tostring(player.UserId)] or DEFAULT_PLAYER_DATA
|
||||
playerData[tostring(player.UserId)] = data
|
||||
return data
|
||||
end
|
||||
```
|
||||
|
||||
<h4>Define public data accessors</h4>
|
||||
|
||||
`getValue()` and `updateValue()` are public-facing functions that other
|
||||
scripts that require this module script can call. In our case, the
|
||||
**CoinService** uses these functions to update a player's coin collection
|
||||
data whenever that player touches a coin.
|
||||
|
||||
```lua
|
||||
function PlayerData.getValue(player, key)
|
||||
return getData(player)[key]
|
||||
end
|
||||
|
||||
function PlayerData.updateValue(player, key, updateFunction)
|
||||
local data = getData(player)
|
||||
local oldValue = data[key]
|
||||
local newValue = updateFunction(oldValue)
|
||||
|
||||
data[key] = newValue
|
||||
return newValue
|
||||
end
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Implement a leaderboard
|
||||
|
||||
You can represent the coin collection data visually with an on-screen
|
||||
leaderboard. Roblox includes a built-in system that automatically generates a
|
||||
leaderboard using a default UI.
|
||||
|
||||
To create the leaderboard:
|
||||
|
||||
1. In the **Explorer** window, create a **ModuleScript** in **ServerStorage**, then
|
||||
rename the module script to **Leaderboard**.
|
||||
|
||||
<img src="../../../../assets/tutorials/record-and-display-player-data/ModuleScript-Renamed-Leaderboard.png" alt="Studio's Explorer window with the Leaderboard script highlighted under ServerStorage." width="320" />
|
||||
|
||||
1. Replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
local Leaderboard = {}
|
||||
|
||||
-- Creating a new leaderboard
|
||||
local function setupLeaderboard(player)
|
||||
local leaderstats = Instance.new("Folder")
|
||||
-- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboard
|
||||
leaderstats.Name = "leaderstats"
|
||||
leaderstats.Parent = player
|
||||
return leaderstats
|
||||
end
|
||||
|
||||
-- Creating a new leaderboard stat value
|
||||
local function setupStat(leaderstats, statName)
|
||||
local stat = Instance.new("IntValue")
|
||||
stat.Name = statName
|
||||
stat.Value = 0
|
||||
stat.Parent = leaderstats
|
||||
return stat
|
||||
end
|
||||
|
||||
-- Updating a player's stat value
|
||||
function Leaderboard.setStat(player, statName, value)
|
||||
local leaderstats = player:FindFirstChild("leaderstats")
|
||||
if not leaderstats then
|
||||
leaderstats = setupLeaderboard(player)
|
||||
end
|
||||
|
||||
local stat = leaderstats:FindFirstChild(statName)
|
||||
if not stat then
|
||||
stat = setupStat(leaderstats, statName)
|
||||
end
|
||||
|
||||
stat.Value = value
|
||||
end
|
||||
|
||||
return Leaderboard
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
The following sections describe how the leaderboard works in more detail.
|
||||
|
||||
<h4>Create a leaderboard</h4>
|
||||
|
||||
The `setupLeaderboard()` function creates a new folder instance named
|
||||
`leaderstats` and sets it as a child of the specified player. Roblox
|
||||
automatically recognizes a folder named `leaderstats` as a container of
|
||||
stats and creates a UI element to display the stats. It requires the values
|
||||
in `leaderstats` to be stored as "value" objects (such as
|
||||
`Class.StringValue`, `Class.IntValue` or `Class.NumberValue`).
|
||||
|
||||
```lua
|
||||
-- Creating a new leaderboard
|
||||
local function setupLeaderboard(player)
|
||||
local leaderstats = Instance.new("Folder")
|
||||
-- 'leaderstats' is a reserved name Roblox recognizes for creating a leaderboard
|
||||
leaderstats.Name = "leaderstats"
|
||||
leaderstats.Parent = player
|
||||
return leaderstats
|
||||
end
|
||||
|
||||
-- Creating a new leaderboard stat value
|
||||
local function setupStat(leaderstats, statName)
|
||||
local stat = Instance.new("IntValue")
|
||||
stat.Name = statName
|
||||
stat.Value = 0
|
||||
stat.Parent = leaderstats
|
||||
return stat
|
||||
end
|
||||
```
|
||||
|
||||
<h4>Update player stats</h4>
|
||||
|
||||
`setStat()` is the only public function in the **Leaderboard** module. It
|
||||
creates stat values for a specified player or the leaderboard itself if it
|
||||
doesn't already exist.
|
||||
|
||||
`Class.Instance:FindFirstChild()|FindFirstChild()` takes the name of an object
|
||||
and returns the object if it exists, or `nil` if it doesn't. It's a common,
|
||||
safe method of finding out if an object exists before you use it.
|
||||
|
||||
```lua
|
||||
-- Updating a player's stat value
|
||||
function Leaderboard.setStat(player, statName, value)
|
||||
local leaderstats = player:FindFirstChild("leaderstats")
|
||||
if not leaderstats then
|
||||
leaderstats = setupLeaderboard(player)
|
||||
end
|
||||
|
||||
local stat = leaderstats:FindFirstChild(statName)
|
||||
if not stat then
|
||||
stat = setupStat(leaderstats, statName)
|
||||
end
|
||||
|
||||
stat.Value = value
|
||||
end
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Integrate the module scripts
|
||||
|
||||
With both the **PlayerData** and **Leaderboard** module scripts complete,
|
||||
require them in the **CoinService** script to manage and display player coin
|
||||
data. To update **CoinService**:
|
||||
|
||||
1. In the **Explorer** window, open the **CoinService** script.
|
||||
2. Replace the existing code with the following code:
|
||||
|
||||
```lua
|
||||
-- Initializing services and variables
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local Players = game:GetService("Players")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
|
||||
-- Modules
|
||||
local Leaderboard = require(ServerStorage.Leaderboard)
|
||||
local PlayerData = require(ServerStorage.PlayerData)
|
||||
|
||||
local coinsFolder = Workspace.World.Coins
|
||||
local coins = coinsFolder:GetChildren()
|
||||
|
||||
local COIN_KEY_NAME = PlayerData.COIN_KEY_NAME
|
||||
local COOLDOWN = 10
|
||||
local COIN_AMOUNT_TO_ADD = 1
|
||||
|
||||
local function updatePlayerCoins(player, updateFunction)
|
||||
-- Update the coin table
|
||||
local newCoinAmount = PlayerData.updateValue(player, COIN_KEY_NAME, updateFunction)
|
||||
|
||||
-- Update the coin leaderboard
|
||||
Leaderboard.setStat(player, COIN_KEY_NAME, newCoinAmount)
|
||||
end
|
||||
|
||||
-- Defining the event handler
|
||||
local function onCoinTouched(otherPart, coin)
|
||||
if coin:GetAttribute("Enabled") then
|
||||
local character = otherPart.Parent
|
||||
local player = Players:GetPlayerFromCharacter(character)
|
||||
if player then
|
||||
-- Player touched a coin
|
||||
coin.Transparency = 1
|
||||
coin:SetAttribute("Enabled", false)
|
||||
updatePlayerCoins(player, function(oldCoinAmount)
|
||||
oldCoinAmount = oldCoinAmount or 0
|
||||
return oldCoinAmount + COIN_AMOUNT_TO_ADD
|
||||
end)
|
||||
|
||||
task.wait(COOLDOWN)
|
||||
coin.Transparency = 0
|
||||
coin:SetAttribute("Enabled", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Setting up event listeners
|
||||
for _, coin in coins do
|
||||
coin:SetAttribute("Enabled", true)
|
||||
coin.Touched:Connect(function(otherPart)
|
||||
onCoinTouched(otherPart, coin)
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
The changes to the original **CoinService** script include:
|
||||
|
||||
- Importing the **PlayerData** and **Leaderboard** modules with the
|
||||
`Global.LuaGlobals.require()` function.
|
||||
- Declaring `COIN_AMOUNT_TO_ADD` as the number of
|
||||
coins to add when a player collects a coin, and `COIN_KEY_NAME` as the key
|
||||
name defined in **PlayerData**.
|
||||
- Creating the helper function
|
||||
`updatePlayerCoins()` to update the player's coin count and associated
|
||||
leaderboard stat.
|
||||
- Replacing the placeholder `print()` statement in
|
||||
`onCoinTouched()` with a call to `updatePlayerCoins()`.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Playtest
|
||||
|
||||
It's time to see if the coin collection is working as intended. When you touch
|
||||
and collect a coin in the 3D world, you should be able to see the amount of coins
|
||||
you've collected on the leaderboard UI. To playtest your experience:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. Move your character to touch a coin. If your scripts are working correctly, the leaderboard UI displays and increase your coin count as you collect more coins.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/record-and-display-player-data/record-and-display-player-data-example.mp4" />
|
||||
</video>
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
---
|
||||
title: Script an upgrade button
|
||||
description: Explains how to communicate with the Roblox server and handle GUI interactions.
|
||||
prev: /tutorials/curriculums/core/scripting/create-player-hazards
|
||||
next: /tutorials/curriculums/core/building/create-basic-visual-effects
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/MSGrq7f25lg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
Players can now collect coins and lose them when they die, but the coins don't
|
||||
do anything, and most of the 3D world is inaccessible without the ability to
|
||||
jump very high. This section of the tutorial teaches you how to finish the logic
|
||||
for your experience by adding an on-screen button that spends coins to increase jumping power.
|
||||
|
||||
## Create the upgrade button
|
||||
|
||||
2D interfaces in Roblox are typically made up of a collection of GUI components
|
||||
inside of a GUI container. In this case, you only need a `Class.TextButton`
|
||||
component that says **Upgrade Jump (5 Coins)** inside a `Class.ScreenGui` container.
|
||||
|
||||
To create the GUI:
|
||||
|
||||
1. In the **Explorer** window, add a new folder into **ReplicatedStorage**, then rename the folder to **Instances**.
|
||||
Any object in **ReplicatedStorage** is accessible to each player's Roblox client, which is where GUIs
|
||||
are displayed.
|
||||
1. Add a **ScreenGui** object into the **Instances** folder.
|
||||
1. Select the **ScreenGui** object, then in the **Properties** window,
|
||||
1. Set **Name** to **JumpPurchaseGui**.
|
||||
1. Disable **ResetOnSpawn** to ensure the GUI stays parented to the player when they
|
||||
respawn.
|
||||
1. In the **Explorer** window, insert a **TextButton** into the **JumpPurchaseGui** container, then rename the text
|
||||
button to **JumpButton**.
|
||||
1. **(Optional)** Customize the button's appearance and position by configuring its properties. Simple suggestions include:
|
||||
- Set the **Text** property to **Upgrade Jump (5 Coins)**.
|
||||
- Set the **TextSize** property to `25`.
|
||||
- Set **AnchorPoint** to `1, 1` and **Position** to `{1, 0},{1, 0}` to move the button to the bottom right corner.
|
||||
|
||||
You'll add the button to the player's GUI later in this tutorial, but before you
|
||||
do, you need to define all the logic and data that is required for the button to
|
||||
work.
|
||||
|
||||
## Define jump power data
|
||||
|
||||
Currently, only coin count is stored for each player in the **PlayerData**
|
||||
module script. You need to also store and update jump power in the same
|
||||
way. Because the functions in **PlayerData** are non-specific to the data being
|
||||
changed, all that's required to store player jump power is to add a `Jump` key
|
||||
and initialize its initial value in `DEFAULT_PLAYER_DATA`.
|
||||
|
||||
To update the **PlayerData** module script to store jumping power:
|
||||
|
||||
1. In the **Explorer** window, open the **PlayerData** module script in
|
||||
**ServerStorage**.
|
||||
2. Replace the code in the script with the following sample, which initializes a
|
||||
`Jump` value for each player alongside their existing `Coins` value:
|
||||
|
||||
```lua
|
||||
local PlayerData = {}
|
||||
|
||||
PlayerData.COIN_KEY_NAME = "Coins"
|
||||
PlayerData.JUMP_KEY_NAME = "Jump"
|
||||
|
||||
local playerData = {
|
||||
--[[
|
||||
[userId: string] = {
|
||||
["Coins"] = coinAmount: number,
|
||||
["Jump"] = jumpPower: number
|
||||
}
|
||||
--]]
|
||||
}
|
||||
|
||||
local DEFAULT_PLAYER_DATA = {
|
||||
[PlayerData.COIN_KEY_NAME] = 0,
|
||||
[PlayerData.JUMP_KEY_NAME] = 0,
|
||||
}
|
||||
|
||||
local function getData(player)
|
||||
local data = playerData[tostring(player.UserId)] or DEFAULT_PLAYER_DATA
|
||||
playerData[tostring(player.UserId)] = data
|
||||
return data
|
||||
end
|
||||
|
||||
function PlayerData.getValue(player, key)
|
||||
return getData(player)[key]
|
||||
end
|
||||
|
||||
function PlayerData.updateValue(player, key, updateFunction)
|
||||
local data = getData(player)
|
||||
local oldValue = data[key]
|
||||
local newValue = updateFunction(oldValue)
|
||||
|
||||
data[key] = newValue
|
||||
return newValue
|
||||
end
|
||||
|
||||
return PlayerData
|
||||
```
|
||||
|
||||
## Update jump power data
|
||||
|
||||
Now that **PlayerData** is able to track jump power, you need to implement logic
|
||||
on the server to upgrade jump power from a player's client request.
|
||||
|
||||
The server and client can communicate through either
|
||||
[Remote events](../../../../scripting/events/remote.md#remote-events) or
|
||||
[Remote functions](../../../../scripting/events/remote.md#remote-callbacks).
|
||||
Remote events do not yield when they are fired and are appropriate for one-way
|
||||
communication. Remote functions yield until they receive a reply, which allows
|
||||
for two-way communication. In this case, the client needs to know if the server
|
||||
upgraded the player's jump power successfully, so a remote function is
|
||||
ideal.
|
||||
|
||||
To implement the jump upgrade:
|
||||
|
||||
1. In the **Explorer** window, open the **Instances** folder in
|
||||
**ReplicatedStorage**.
|
||||
1. Insert a **RemoteFunction** into the **Instances** folder, then rename the remote
|
||||
function to **IncreaseJumpPowerFunction**. You always create remote functions in
|
||||
**ReplicatedStorage** because both the client and server must be able to
|
||||
access them.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-an-upgrade-button/Insert-RemoteFunction.png" alt="Studio's Explorer window with the IncreaseJumpPowerFunction script highlighted under the Instances folder." width="320" />
|
||||
|
||||
1. In the **Explorer** window, select **StarterPlayer**.
|
||||
1. In the **Properties** window, enable the **CharacterUseJumpPower** property.
|
||||
By default, a character's jump power value does not define the amount that a
|
||||
character jumps, so this needs to be enabled.
|
||||
1. In the **Explorer** window, insert a new script into **ServerScriptService**, then
|
||||
rename the script to **JumpService**. This script will contain the logic for jump upgrades.
|
||||
1. Replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
-- Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
-- Modules
|
||||
local Leaderboard = require(ServerStorage.Leaderboard)
|
||||
local PlayerData = require(ServerStorage.PlayerData)
|
||||
|
||||
-- Events
|
||||
local IncreaseJumpPowerFunction = ReplicatedStorage.Instances.IncreaseJumpPowerFunction
|
||||
|
||||
local JUMP_KEY_NAME = PlayerData.JUMP_KEY_NAME
|
||||
local COIN_KEY_NAME = PlayerData.COIN_KEY_NAME
|
||||
local JUMP_POWER_INCREMENT = 30
|
||||
local JUMP_COIN_COST = 5
|
||||
|
||||
local function updateJumpPower(player, updateFunction)
|
||||
-- Update the jump power table
|
||||
local newJumpPower = PlayerData.updateValue(player, JUMP_KEY_NAME, updateFunction)
|
||||
|
||||
-- Update the players jump power
|
||||
local character = player.Character or player.CharacterAdded:Wait()
|
||||
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
|
||||
if humanoid then
|
||||
humanoid.JumpPower = newJumpPower
|
||||
|
||||
-- Update the jump leaderboard
|
||||
Leaderboard.setStat(player, JUMP_KEY_NAME, newJumpPower)
|
||||
end
|
||||
end
|
||||
|
||||
local function onPurchaseJumpIncrease(player)
|
||||
local coinAmount = PlayerData.getValue(player, COIN_KEY_NAME)
|
||||
if coinAmount < JUMP_COIN_COST then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Increase player's jump power
|
||||
updateJumpPower(player, function(oldJumpPower)
|
||||
oldJumpPower = oldJumpPower or 0
|
||||
return oldJumpPower + JUMP_POWER_INCREMENT
|
||||
end)
|
||||
-- Update the coin table
|
||||
local newCoinAmount = PlayerData.updateValue(player, COIN_KEY_NAME, function(oldCoinAmount)
|
||||
return oldCoinAmount - JUMP_COIN_COST
|
||||
end)
|
||||
-- Update the coin leaderboard
|
||||
Leaderboard.setStat(player, COIN_KEY_NAME, newCoinAmount)
|
||||
return true
|
||||
end
|
||||
|
||||
local function onCharacterAdded(player)
|
||||
-- Reset player's jump power when the character is added
|
||||
updateJumpPower(player, function(_)
|
||||
return 0
|
||||
end)
|
||||
end
|
||||
|
||||
-- Initialize any players added before connecting to PlayerAdded event
|
||||
for _, player in Players:GetPlayers() do
|
||||
onCharacterAdded(player)
|
||||
end
|
||||
|
||||
-- Normal initialization of players from PlayerAdded event
|
||||
local function onPlayerAdded(player)
|
||||
player.CharacterAdded:Connect(function()
|
||||
onCharacterAdded(player)
|
||||
end)
|
||||
end
|
||||
|
||||
local function onPlayerRemoved(player)
|
||||
updateJumpPower(player, function(_)
|
||||
return nil
|
||||
end)
|
||||
end
|
||||
|
||||
IncreaseJumpPowerFunction.OnServerInvoke = onPurchaseJumpIncrease
|
||||
Players.PlayerAdded:Connect(onPlayerAdded)
|
||||
Players.PlayerRemoving:Connect(onPlayerRemoved)
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
The following sections describe the code in more detail.
|
||||
|
||||
- **Update the jump power data** - `updateJumpPower()` updates the jump power of the player and the leaderboard to
|
||||
provide visual feedback. This function resembles the code that damages players
|
||||
in [Create player hazards](./create-player-hazards.md). Provided a
|
||||
`Class.Player.Character|Character` model and `Class.Humanoid` exist for the
|
||||
player being upgraded, the function updates the
|
||||
`Class.Humanoid.JumpPower|JumpPower` property to the new value stored by
|
||||
**PlayerData**, increasing it by 30. If you want your experience to last slightly
|
||||
longer, you can decrease this number.
|
||||
|
||||
- **Validate server requests** - `onPurchaseJumpIncrease()` first checks that the
|
||||
player actually has the number of coins required to purchase the upgrade. **All
|
||||
requests** from clients to the server should be **validated** to prevent bad
|
||||
actors submitting false requests and exploiting your experience.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Add the button to the player GUI
|
||||
|
||||
A `Class.ScreenGui` object only shows on-screen if it is parented to a player's
|
||||
`Class.PlayerGui` object. By default, this contains the
|
||||
system GUI such as the chat window. You now need to create a
|
||||
script in **ReplicatedStorage** to copy the upgrade button into each
|
||||
player's GUI and implement behavior for when it's pressed.
|
||||
|
||||
To add the button to the player's GUI when they join:
|
||||
|
||||
1. In the **Explorer** window, create a **Script** in **ReplicatedStorage**.
|
||||
1. Select the script, then in the **Properties** window,
|
||||
1. Set **Name** to **JumpButtonClickHandler**.
|
||||
1. Set **RunContext** to **Client**. This tells the engine to run always run this script
|
||||
on the client to optimize network communication.
|
||||
1. In the open script, replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local player = Players.LocalPlayer
|
||||
local playerGui = player.PlayerGui
|
||||
|
||||
local IncreaseJumpPowerFunction = ReplicatedStorage.Instances.IncreaseJumpPowerFunction
|
||||
local jumpPurchaseGui = ReplicatedStorage.Instances.JumpPurchaseGui
|
||||
local jumpButton = jumpPurchaseGui.JumpButton
|
||||
|
||||
local function onButtonClicked()
|
||||
local success, purchased = pcall(IncreaseJumpPowerFunction.InvokeServer, IncreaseJumpPowerFunction)
|
||||
if not success then
|
||||
-- purchased will be the error message if success is false
|
||||
error(purchased)
|
||||
elseif success and not purchased then
|
||||
warn("Not enough coins!")
|
||||
end
|
||||
end
|
||||
|
||||
jumpButton.Activated:Connect(onButtonClicked)
|
||||
|
||||
-- Add the JumpPurchaseGui to the player's Gui
|
||||
jumpPurchaseGui.Parent = playerGui
|
||||
```
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
The following sections describe the code in more detail.
|
||||
- **Obtain references to the GUI and server function** - The variables
|
||||
`IncreaseJumpPowerFunction`, `jumpPurchaseGui`, and `jumpButton` contain
|
||||
references to the function and GUI that calls the function that you'll
|
||||
need later.
|
||||
- **Define the event handler** - `onButtonClicked()` defines logic for
|
||||
when users click the upgrade button. It uses `Global.LuaGlobals.pcall|pcall()` (protected call) to invoke the `Class.RemoteFunction`. Any client-server communication like this requires `Global.LuaGlobals.pcall|pcall()` to handle errors or connection issues.
|
||||
- **Connect the handler to the button** - The `Class.GuiButton.Activated|Activated` event is compatible on all platforms, including mouse, touchscreen, or gamepad contexts. It triggers when a **click**, **touch**, or **gamepad button** is released.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Playtest
|
||||
|
||||
You should now be able to purchase jump upgrades for coins using the upgrade button. To test out the project:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. If your scripts are working correctly, a button for purchasing jumping power appears on-screen. Try clicking the button before you collect any coins to check that it doesn't award you additional jumping power, then try collecting some coins and see if the upgrade works when you click again.
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/script-an-upgrade-button/script-an-upgrade-button-example.mp4" />
|
||||
</video>
|
||||
|
||||
Now that the code is complete, try balancing the experience through the quantity and positions of the coins. Add more coins if the pace feels too slow, or subtract coins and put them in challenging places if it feels too fast and easy.
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
---
|
||||
title: Create a coin collection mechanic
|
||||
description: Explains how to implement event-driven logic in an experience.
|
||||
next: /tutorials/curriculums/core/scripting/record-and-display-player-data
|
||||
prev: /tutorials/curriculums/core/building/greybox-a-playable-area
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/pg6nWVARoDM?si=93UpoO1ll2dZDhzO" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
Now that you have a 3D world, this section of the tutorial teaches you how to add your
|
||||
first script to define a coin collecting mechanic. This mechanic allows players to collect
|
||||
coins, and disables the collection of a coin if it's been recently collected.
|
||||
|
||||
## Create the coins
|
||||
|
||||
Before you can script anything, you need to have placeholder objects in the world to use as
|
||||
your coins. Like the sea stack platforms you made in the previous section, the coins can be
|
||||
simple `Class.Part` objects.
|
||||
|
||||
To create the coins:
|
||||
|
||||
1. In the **Explorer** window, add a new folder into the **World** folder, then rename it **Coins**.
|
||||
1. Insert a **cylinder** part into the **Coins** folder, then rename the part to **Coin**.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/New-Single-Coin.png" alt="Studio's Explorer window with the Coin part highlighted. The hierarchy of the Workspace to World folder to Coins folder to Coin part is also highlighted." width="320" />
|
||||
|
||||
1. Select the part, then in the **Properties** window,
|
||||
|
||||
- Set **BrickColor** to **Gold**.
|
||||
- Set **Material** to **Metal**.
|
||||
- Set **Size** to `0.6, 8, 4`.
|
||||
- Disable **CanCollide**. This tells the engine that other parts can pass through the coin, meaning players
|
||||
can walk through the coins in order to collect them.
|
||||
- Enable **Anchored**. This tells the engine to never change the position of the coin due to any physics-related
|
||||
simulation, meaning players can touch the coin without affecting its location.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Single-Coin-In-Viewport.jpg" alt="A close up view of a gold coin next to two gray cylinder sea stacks on the island." width="600" />
|
||||
|
||||
1. Duplicate a few more coins and position them around the map for testing purposes.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Duplicated-Coins.png" alt="Studio's Explorer window with multiple Coin parts highlighted under the Coins folder." width="320" />
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Multiple-Coins-In-Level.jpg" alt="A view of multiple coins on the island and two gray cylinder sea stacks." />
|
||||
|
||||
Your cylinder parts now look like coins and prevent physics simulation, but you need to add
|
||||
logic to the coins so players can collect them.
|
||||
|
||||
## Create the script
|
||||
|
||||
To have the coins be collectable, you want to react to players touching them.
|
||||
The Roblox Engine can notify you when something touches a coin, but you need to
|
||||
declare that in a script. To create a script:
|
||||
|
||||
1. In the **Explorer** window, hover over **ServerScriptService** and click the
|
||||
**⊕** button. A contextual menu displays.
|
||||
1. From the contextual menu, select **Script**. A new script displays under **ServerScriptService**,
|
||||
which tells the engine to run the script on the server, and prevents clients from accessing the code.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Insert-Script.png" alt="Studio's Explorer window with both ServerScriptService's plus icon and Script object highlighted." width="320" />
|
||||
|
||||
1. Rename the script to **CoinService**.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Rename-New-Script.png" alt="Studio's Explorer window with the CoinService script highlighted under ServerScriptService." width="320" />
|
||||
|
||||
1. Replace the default code with the following code:
|
||||
|
||||
```lua
|
||||
-- Initializing services and variables
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local coinsFolder = Workspace.World.Coins
|
||||
local coins = coinsFolder:GetChildren()
|
||||
|
||||
local COOLDOWN = 10
|
||||
|
||||
-- Defining the event handler
|
||||
local function onCoinTouched(otherPart, coin)
|
||||
if coin:GetAttribute("Enabled") then
|
||||
local character = otherPart.Parent
|
||||
local player = Players:GetPlayerFromCharacter(character)
|
||||
if player then
|
||||
-- Player touched a coin
|
||||
coin.Transparency = 1
|
||||
coin:SetAttribute("Enabled", false)
|
||||
print("Player collected coin")
|
||||
task.wait(COOLDOWN)
|
||||
coin.Transparency = 0
|
||||
coin:SetAttribute("Enabled", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Setting up event listeners
|
||||
for _, coin in coins do
|
||||
coin:SetAttribute("Enabled", true)
|
||||
coin.Touched:Connect(function(otherPart)
|
||||
onCoinTouched(otherPart, coin)
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
Now, whenever a player touches a coin, the coin disappears for 10 seconds,
|
||||
and the output log prints `Player collected coin`.
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
|
||||
<Typography variant="h4">Code explanation</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
The following sections describe how the script works in more detail.
|
||||
|
||||
<h4>Initialize services and variables</h4>
|
||||
|
||||
Like with a lot of the code you've probably written in other languages, you
|
||||
define variables that you need later at the top of the script. Our code does
|
||||
the following:
|
||||
|
||||
- **Obtain service instances** - Roblox services provide built-in
|
||||
functionality for common features. The script first obtains instances of the
|
||||
`Class.Workspace` service, which contains every object in the 3D world, and
|
||||
the `Class.Player` service, which manages and contains all players connected
|
||||
to your experience.
|
||||
- **Obtain references to all coins** - The script then queries the 3D
|
||||
workspace for all references to coin objects with the
|
||||
`Class.Instance:GetChildren()|GetChildren()` method. This method returns an
|
||||
array containing everything parented to the object it's associated with,
|
||||
which in this case is the `Workspace.World.Coins` folder you created
|
||||
previously.
|
||||
- **Defines a global variable** - The `COOLDOWN` variable is used later to
|
||||
define how long to disable a coin after it's collected.
|
||||
|
||||
```lua title="Initializing Services and Variables"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local coinsFolder = Workspace.World.Coins
|
||||
local coins = coinsFolder:GetChildren()
|
||||
|
||||
local COOLDOWN = 10
|
||||
...
|
||||
```
|
||||
|
||||
<h4>Define the event handler</h4>
|
||||
|
||||
The Roblox Engine physically simulates the 3D world and handles a lot of the
|
||||
logic to handle events related to rendering, physics, and networking. When
|
||||
you're interested in scripting your own logic during some of these events,
|
||||
you can listen for and handle them, while letting the engine do the rest. In
|
||||
this case, you listen for and handle events related to coins being touched.
|
||||
The script defines the logic for handling this event in the `onCoinTouched()`
|
||||
method, which does the following:
|
||||
|
||||
- **Detects whether the coin is enabled** - Every `Class.Instance` has an
|
||||
`Enabled` boolean attribute that defines whether or not the object exists in
|
||||
the 3D world. You can get instance attributes with the
|
||||
`Class.Instance:GetAttribute()|GetAttribute()` method.
|
||||
- **Detects whether a player touched the coin** - If a coin is enabled, the
|
||||
method uses the player service to check if the object that touched the coin
|
||||
was indeed a player. When a touch event occurs, the Roblox Engine passes the
|
||||
object that touched the coin as an `otherPart` parameter. The script checks to
|
||||
see if the parent of `otherPart` belongs to a player.
|
||||
- **Disables the coin if a player touched it, and re-enables it after 10
|
||||
seconds** - Finally, if a player touched the coin, the method disables the
|
||||
coin, waits for 10 seconds, and then re-enables the coin for collection.
|
||||
`Library.task.wait()` is used instead of `wait()` because it provides better
|
||||
performance by not pausing code execution entirely, allowing tasks in other
|
||||
threads to run concurrently.
|
||||
|
||||
```lua title="Defining the Event Handler"
|
||||
local function onCoinTouched(otherPart, coin)
|
||||
if coin:GetAttribute("Enabled") then
|
||||
local character = otherPart.Parent
|
||||
local player = Players:GetPlayerFromCharacter(character)
|
||||
if player then
|
||||
-- Player touched a coin
|
||||
coin.Transparency = 1
|
||||
coin:SetAttribute("Enabled", false)
|
||||
print("Player collected coin")
|
||||
task.wait(COOLDOWN)
|
||||
coin.Transparency = 0
|
||||
coin:SetAttribute("Enabled", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
<h4>Connect the event handler</h4>
|
||||
|
||||
All simulated 3D objects inherit from `Class.BasePart` and therefore have a
|
||||
`Class.BasePart.Touched|Touched()` event. The following loop connects the
|
||||
`onTouchedEvent()` handler to every coin's touch event by doing the
|
||||
following:
|
||||
|
||||
- **Loop through all the coins** - Loop through each of the coins using general iteration.
|
||||
- **Connect the handler to the event** - In each iteration of the loop, the
|
||||
coin is enabled by default, so it's visible in the 3D world during the
|
||||
initial start of the experience. The `onCoinTouched()` handler method is also
|
||||
connected to the `Touched` event of the coin so that it runs every time the
|
||||
event occurs. When the engine detects a touch, it also passes in the
|
||||
object that touched the object, `otherPart`.
|
||||
|
||||
```lua title="Connecting the Event Handler"
|
||||
for _, coin in coins do
|
||||
coin:SetAttribute("Enabled", true)
|
||||
coin.Touched:Connect(function(otherPart)
|
||||
onCoinTouched(otherPart, coin)
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
## Playtest the mechanic
|
||||
|
||||
It's time to see if the coin collection mechanic works as intended. To playtest
|
||||
your experience:
|
||||
|
||||
1. Choose **Test** from the dropdown menu and click the **Play** button to its right to begin the playtest.
|
||||
|
||||
<img src="../../../../assets/studio/general/Mezzanine-Testing-Mode-Test.png" width="800" alt="Test option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
1. Move your character to touch a coin. If your scripts are working correctly, the [Output](../../../../studio/output.md) window displays `Player collected coin`, and the coin disappears for 10 seconds before re-appearing.
|
||||
|
||||
<img src="../../../../assets/tutorials/script-game-behavior/Output-Collect-Coin.png" alt="Studio's Output window that displays confirmation that the player collected a coin." width="700" />
|
||||
|
||||
<video controls loop muted>
|
||||
<source src="../../../../assets/tutorials/script-game-behavior/script-game-behavior-coin-collection.mp4" />
|
||||
</video>
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
---
|
||||
title: Advanced curriculum paths
|
||||
description: An overview of creation curriculum paths covering topics like modeling, scripting, and gameplay logic.
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/DKzFkn7q8MQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br /> <br />
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Prerequisites</Typography>
|
||||
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" component="p">
|
||||
If you've never used Roblox Studio or coded before, start here before following any of the advanced curriculum paths:
|
||||
</Typography>
|
||||
|
||||
- [Studio lesson](building/index.md) - Install
|
||||
and configure Roblox Studio.
|
||||
- [Create your first experience](core/index.md) - Build a simple experience.
|
||||
- [Luau basics](../../tutorials/fundamentals/coding-1/coding-fundamentals.md) - Learn the fundamentals of programming with Luau.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<Card>
|
||||
|
||||
<CardContent>
|
||||
|
||||
<h2 style={{marginBottom: 12}}>Environmental art</h2>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" component="p">
|
||||
Learn the foundational skills you need to become an environmental artist.
|
||||
</Typography>
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe src="https://www.youtube-nocookie.com/embed/nwShvDmFHWc?si=2gk0n6cb9uq-48Ni" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "90%", height: "90%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<>
|
||||
<Stepper activeStep={3} orientation="vertical">
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Create a plan for your environment and necessary assets.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Plan</h5>
|
||||
</StepLabel>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel
|
||||
optional="Assemble and apply your asset library to the 3D space to bring your world to life.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Construct</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Configure your assets and Studio settings to keep your frame rate and performance levels high." >
|
||||
<h5 style={{marginTop: 36}}>Optimize</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<a href="../curriculums/environmental-art/index.md">
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large">
|
||||
Start creating
|
||||
</Button>
|
||||
</a>
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
|
||||
<br /> <br />
|
||||
|
||||
<Card>
|
||||
|
||||
<CardContent>
|
||||
|
||||
<h2 style={{marginBottom: 12}}>Gameplay scripting</h2>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" component="p">
|
||||
Learn the programming skills you need to introduce gameplay to the laser tag environment.
|
||||
</Typography>
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe src="https://www.youtube-nocookie.com/embed/7iJKUUiKc0Y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "90%", height: "90%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<>
|
||||
<Stepper activeStep={3} orientation="vertical">
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Spawn players into the environment, and respawn them once their health reaches zero.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Spawn</h5>
|
||||
</StepLabel>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Create a blast mechanic that is both accurate in the 3D space and satisfying to players.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Blast</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Implement laser detecting behavior that handles blast direction, verifies collision, and reduces player health." >
|
||||
|
||||
<h5 style={{marginTop: 36}}>Detect</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<a href="../curriculums/gameplay-scripting/index.md">
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large">
|
||||
Start creating
|
||||
</Button>
|
||||
</a>
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
|
||||
<br /> <br />
|
||||
|
||||
<Card>
|
||||
|
||||
<CardContent>
|
||||
|
||||
<h2 style={{marginBottom: 12}}>User interface design</h2>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" component="p">
|
||||
Learn the design skills you need to implement UI that informs players of the laser tag gameplay.
|
||||
</Typography>
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe src="https://www.youtube-nocookie.com/embed/eqW9SINYeWg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "90%", height: "90%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={6} XSmall={12} direction="column">
|
||||
|
||||
<>
|
||||
<Stepper activeStep={3} orientation="vertical">
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Plan an art style that drives all design decisions for your UI elements.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Plan</h5>
|
||||
</StepLabel>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Wireframe the structure and flow of the information you want to communicate to your players.">
|
||||
|
||||
<h5 style={{marginTop: 36}}>Wireframe</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
<Step style={{marginTop: -36}}>
|
||||
<StepLabel optional="Implement your UI layouts in Studio using both built-in and custom UI elements." >
|
||||
|
||||
<h5 style={{marginTop: 36}}>Implement</h5>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
<a href="../curriculums/user-interface-design/index.md">
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large">
|
||||
Start creating
|
||||
</Button>
|
||||
</a>
|
||||
</CardContent>
|
||||
|
||||
</Card>
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
---
|
||||
title: Assemble an asset library
|
||||
description: Explains how to import and configure your assets within a central location in your experience.
|
||||
next: /tutorials/curriculums/environmental-art/construct-your-world
|
||||
prev: /tutorials/curriculums/environmental-art/develop-polished-assets
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/0shiHaGe87s?si=xILCuyVy8dIviZ2U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Assembling an asset library** is the process of importing and configuring a collection of assets in a central location within your experience for easy access and reuse. This process can save you significant time as you prepare to construct your environment, especially if you configure each asset's values to enhance performance and optimize memory usage on mid to low-end devices.
|
||||
|
||||
Using the [Environment Art - Assembling](https://www.roblox.com/games/14447787049/Environment-Art-Assembling) `.rbxl` file as a reference, this section of the environmental art curriculum shows you how to assemble an asset library from your polished assets, including step-by-step instructions on:
|
||||
|
||||
- Creating custom materials for your terrain from your tileable textures.
|
||||
- Creating SurfaceAppearance objects for your trim sheet, and applying them to unwrapped meshes in your asset library.
|
||||
- Setting rendering parameters for your assets to ensure optimal performance across all devices.
|
||||
- Converting your assets into packages so that you can update and reuse them across all of your projects.
|
||||
|
||||
After you complete this section, you will learn how to utilize your asset library in creative ways to replace or convert the greybox environment, and add final touches that enhance the overall 3D space.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/Overview.png" alt="The sample laser tag experience's modular and prop kit." width="100%"/>
|
||||
|
||||
<Alert severity="info">
|
||||
Before you begin to compile your asset library by importing your tileable textures, trim sheets, modular assets, and props into Studio, it's best practice to arrange and organize all of your content in a folder structure according to their data type, such as trim sheets, decals, modular assets, and props. This can provide you an ease of reference as you create custom materials and visual treatments.
|
||||
</Alert>
|
||||
|
||||
## Create custom materials
|
||||
|
||||
Studio represents custom materials as `Class.MaterialVariant` objects within the `Class.MaterialService`. These `Class.MaterialVariant` objects have four properties that combine the four tileable [texture maps](../../../art/modeling/surface-appearance.md#texture-maps) to create a high-quality custom material with physical characteristics:
|
||||
|
||||
- `Class.MaterialVariant.ColorMap` – Represents the albedo texture map.
|
||||
- `Class.MaterialVariant.MetalnessMap` – Represents the metalness texture map.
|
||||
- `Class.MaterialVariant.NormalMap` – Represents the normal texture map.
|
||||
- `Class.MaterialVariant.RoughnessMap` – Represents the roughness texture map.
|
||||
|
||||
After you supply the texture maps to these properties, you can apply your new custom material to both parts, meshes, and terrain. You can use or modify the sample texture map `.png` files to create the Lumpy Moss, Flowering Lumpy Moss, Lumpy Moss with Stones, and Stones custom materials within the final sample laser tag environment, or you can use your own that you designed in the previous section of the tutorial.
|
||||
|
||||
To create `Class.MaterialVariant` objects for your tileable textures:
|
||||
|
||||
1. From the **Window** ⟩ **3D** menu, select **Material** to open the **Material Manager**.
|
||||
2. In the **Materials** list, select the base material that most aligns with your tileable texture. For example, if you were creating a `Class.MaterialVariant` object for the Lumpy Moss tileable texture, choose the default ground material so the material inherits its physical properties.
|
||||
3. In the upper toolbar, click the **⊕** icon. A new `Class.MaterialVariant` displays in the palette with an icon in the bottom-right corner that indicates it's a custom material.
|
||||
|
||||
<img src="../../../assets/studio/material-manager/Custom-Material-Icon.png" width="100%" alt="New MaterialVariant tile in Material Manager with icon to indicate a custom material" />
|
||||
|
||||
4. In the **Inspector**, navigate to the **General** section, then rename the material to align with the name of your tileable texture.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-4.jpg" alt="The Inspector view with the name of the material highlighted." width="40%"/>
|
||||
|
||||
5. In the **Texture Maps** section, on the right-hand side of **Color**, click the **Import** button. A file browser displays.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-5.jpg" alt="The Texture Maps section of the Inspector view with the Import button highlighted." width="40%"/>
|
||||
|
||||
6. In the file browser, select the **Albedo** texture map `.png` file for the corresponding tileable texture, then the **Open** button. The file browser closes, and the new color map displays with its asset ID.
|
||||
7. Repeat this process for the **Metalness**, **Normal**, and **Roughness** texture maps. The custom material updates to reflect the texture maps.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-7a.jpg" alt="All texture maps for the Moss_Lumpy_A material." width="85%"/>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-7b.jpg" alt=" sphere part with the Moss_Lumpy_A material applied." width="100%"/>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
8. In the **Overrides** section, enable the **Set as Override** toggle to ensure that when you apply ground to your terrain, Studio uses this custom material.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-8.jpg" alt="The Set as Override toggle enabled." width="40%"/>
|
||||
|
||||
9. **(Optional)** If you are creating a custom material that represents an organic material,
|
||||
|
||||
1. In the **Tiling** section, click the **Pattern** dropdown.
|
||||
1. Select **Organic** to randomize the output and reduce visible tiling.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CustomMaterials-9.jpg" alt="The Tiling section of the Inspector view with its settings." width="40%"/>
|
||||
|
||||
10. Repeat this process for each tileable texture you want to include in your experience.
|
||||
|
||||
## Create SurfaceAppearance packages
|
||||
|
||||
Studio utilizes trim sheets within `Class.SurfaceAppearance` objects that you can parent to `Class.MeshPart|MeshParts` that contain UV data. Similar to custom materials, `Class.SurfaceAppearance` objects have four properties that combine the four trim sheet texture maps to create a high-quality 3D visual surface treatment:
|
||||
|
||||
`Class.SurfaceAppearance.ColorMap` – Represents the albedo texture map.
|
||||
`Class.SurfaceAppearance.MetalnessMap` – Represents the metalness texture map.
|
||||
`Class.SurfaceAppearance.NormalMap` – Represents the normal texture map.
|
||||
`Class.SurfaceAppearance.RoughnessMap` – Represents the roughness texture map.
|
||||
|
||||
After you supply the texture maps to these properties, you can make the `Class.SurfaceAppearance` object a child of a `Class.MeshPart` with uv data that maps to your trim sheet, and the uv data automatically applies to the parent mesh. It's for this reason that it's useful to make the `Class.SurfaceAppearance` object a [package](../../../projects/assets/packages.md) so that you can reuse the same `Class.SurfaceAppearance` object across all of your modular assets and props. For example, the following meshes all have uv data that maps to the same trim sheet layout that's represented within a single `Class.SurfaceAppearance` object package.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SAPackages-Intro.jpg" alt="" width="80%"/>
|
||||
|
||||
You can use or modify the sample texture map `.png` files to create a `Class.SurfaceAppearance` object that respects the uv data for the sample modular assets and props within the final sample laser tag environment, or you can use your own that you designed in the previous section of the tutorial that apply to your own modular assets and props.
|
||||
|
||||
To create `Class.SurfaceAppearance` packages that you can child to your modular kit and props:
|
||||
|
||||
1. Insert a **SurfaceAppearance** object into `Class.Workspace`.
|
||||
2. Select the **SurfaceAppearance** object, then in the **Properties** window, select the **ColorMap** property. A pop-up displays.
|
||||
3. Click the **Add Image…** button. A file browser displays.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SAPackages-3.jpg" alt="The ColorMap pop-up with the Add Image button highlighted." width="30%"/>
|
||||
|
||||
4. Select the **Albedo** texture map for the corresponding tileable texture, then the **Open** button. The file browser closes, and the **ColorMap** property updates with a new asset ID.
|
||||
5. Repeat this process for the **MetalnessMap**, **NormalMap**, and **RoughnessMap** properties, selecting their respective texture maps from the file browser.
|
||||
6. In the **Explorer** window, right-click the **SurfaceAppearance** object, then select **Convert to Packag**e from the contextual menu. The **Convert to Package** dialog displays.
|
||||
7. Fill out the **Title** and **Description** fields, set the ownership to yourself or a group, then click the **Submit** button. Once complete, a chain link symbol displays over the SurfaceAppearance object's icon to identify it as a package.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SAPackages-7.jpg" alt="The Surface Appearance icon with a chain link symbol." width="30%"/>
|
||||
|
||||
8. **(Optional)** Select the **SurfaceAppearance** object's child **PackageLink** object, then in the **Properties** window, enable **AutoUpdate** to automatically update the package if you make any modifications to the **SurfaceAppearance** object.
|
||||
|
||||
## Import modular assets and props
|
||||
|
||||
Studio represents the modular assets and props that you import as `Class.Model` objects with child `Class.MeshPart|MeshParts` for each component of the asset. For example, if you import a wall section with a top trim, bottom trim, and wall component, Studio represents the `.fbx` or `.obj` file as a **Wall_Section** `Class.Model` with separate child **Top_Trim**, **Bottom_Trim**, and **Wall** `Class.MeshPart|MeshParts`.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/Importing-Intro.png" alt="" width="30%"/>
|
||||
|
||||
Using the 3D Importer, you can import the sample modular kit and props into Studio for use in your asset library, or you can import any assets you designed in the previous section of the tutorial. For more information on this tool's functionality, see [3D Importer](../../../art/modeling/3d-importer.md).
|
||||
|
||||
To import your modular assets and props into the experience:
|
||||
|
||||
1. From the **File** menu, select **Import 3D**. A file browser displays.
|
||||
2. Select a `.fbx` file for either a modular asset or prop.
|
||||
3. Verify the object preview and check that the import settings are correct for your object.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/Importing-3.jpg" alt="" width="80%"/>
|
||||
|
||||
4. Verify any [warning or error messages](../../../art/modeling/3d-importer.md#warnings-and-errors).
|
||||
5. Click **Import**. The asset displays in the **Explorer** window and in the viewport.
|
||||
6. Repeat this process for each modular asset and prop.
|
||||
7. Move all of your assets into one area of your experience near your greybox geometry. This zone is your asset library.
|
||||
|
||||
## Apply SurfaceAppearance data
|
||||
|
||||
When you go through the process of unwrapping the UVs of a mesh in third-party modeling software, the software stores the UV data within the mesh's `.fbx` or `.obj` file. When you import that mesh into Studio, the resulting `Class.MeshPart` object retains that data, but you still need to apply a `Class.SurfaceAppearance` object with texture map properties from your trim sheet in order to display your trim textures on the asset.
|
||||
|
||||
To apply `Class.SurfaceAppearance` texture map data to unwrapped meshes in your asset library:
|
||||
|
||||
1. In the **Explorer** window, click your **SurfaceAppearance** package, then press <kbd>Ctrl</kbd><kbd>D</kbd> (<kbd>⌘</kbd><kbd>D</kbd>) to duplicate it.
|
||||
2. Child the duplicate **SurfaceAppearance** package to either a modular asset or a prop. The asset applies its UV data to the texture maps and displays its visual treatment.
|
||||
3. Repeat this process for each modular asset and prop.
|
||||
|
||||
## Set physics and rendering parameters
|
||||
|
||||
Now that you have your assets within Studio, it's important to set physics and rendering parameters that allow the assets to retain the highest possible visual quality across devices with memory and GPU limitations. In general, the more closely you adhere to the guidance in this section, the more performant you make your experience. However, it's important to carefully consider the context of where you plan to place each asset within the overall environment to ensure you maintain your aesthetic goals and gameplay requirements.
|
||||
|
||||
### Anchored
|
||||
|
||||
The `Class.BasePart.Anchored` property determines whether the Roblox Engine's physics system affects the position of the object. When you enable this property for a `Class.Part` or `Class.MeshPart`, it removes the object from the physical calculations of the dynamic simulation that's always running in experience, meaning the object will never change position due to gravity or collision from other objects.
|
||||
|
||||
Objects that don't simulate a physics response are cheaper to render because they don't take up the GPU that's necessary for those physics calculations. When you free up GPU, you improve performance for your experience, especially on low-end devices that need to save GPU for fast-paced gameplay. It's for this reason that **every** asset aside from doors in the final sample laser tag environment are anchored.
|
||||
|
||||
### CanCollide
|
||||
|
||||
The `Class.BasePart.CanCollide` property determines whether the object can physically interact with other objects. When you enable this property for a `Class.Part` or `Class.MeshPart`, the object is impenetrable, and the Roblox Engine accounts for it in physics calculations. These calculations can impact your experience's performance when there are many objects the engine must consider in its physics calculations.
|
||||
|
||||
To alleviate some of this impact on performance, disable this property for objects that the user will never interact with. For example, the final sample laser tag environment disables this property for all foliage.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SettingParameters-CanCollide.png" alt="" width="60%"/>
|
||||
|
||||
### CanTouch
|
||||
|
||||
The `Class.BasePart.CanTouch` property determines if `Class.BasePart.Touched|Touched` and `Class.BasePart.TouchEnded|TouchEnded` events fire on the object. When you enable this property for a `Class.Part` or `Class.MeshPart`, the Roblox Engine checks the object's touch event state to see if it needs to trigger or stop an event.
|
||||
|
||||
This process happens for every single frame, which can take up a significant amount of memory if the engine needs to check the touch event state for many objects at once. To alleviate some of this impact on memory, only enable this property for objects that must trigger an event.
|
||||
|
||||
### CanQuery
|
||||
|
||||
The `Class.BasePart.CanQuery` property determines whether the Roblox Engine considers the object during spatial query operations, such as raycasting. Studio enables this property for every `Class.Part` or `Class.MeshPart` by default, meaning that the engine checks to see if each object needs to call a spatial query operation. This process occurs for every single frame, which can take up a significant amount of memory when the engine needs to make these checks for many objects at once.
|
||||
|
||||
For this reason, it's recommended to disable this property for objects that the Roblox Engine doesn't need to consider for spatial query operations. When making decisions about where to disable this property, consider how each asset impacts gameplay. For example, the final sample laser tag environment keeps this property enabled for every wall in the building because the engine needs to account for these surfaces as users fire lasers from their laser tag gun. If the engine doesn't account for these assets, then the laser would shoot straight through the building as though it wasn't there at all.
|
||||
|
||||
### CastShadow
|
||||
|
||||
The `Class.BasePart.CastShadow` property determines whether the object casts a shadow. When you enable this property for a `Class.Part` or `Class.MeshPart`, the Roblox Engine calculates every vertex location of the object at runtime, then draws a raycast from the sun to any of its neighboring objects until it collides with another object to create the shadow.
|
||||
|
||||
These calculations can come at a performance cost, especially when you have many objects of geometric complexity. This is because objects with geometric complexity have more polygons than objects that are geometrically simple, meaning they have more vertices that the engine needs to calculate for the object's shadow. The fewer polygons in an object, the faster the operation and cheaper its shadow.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SettingParameters-CastShadow1.png" alt="A wall section with few polygons, and a plant with many polygons casting shadows on a white wall." width="80%"/>
|
||||
|
||||
Shadows can provide a great sense of realism to objects with 3D geometrical depth, so when you're deciding where to disable this property, consider where objects provide a significant visual improvement to the environment, and where users will notice missing shadows. For example, the complex shadows of foliage can provide a great sense of immersion in the outdoor space where users are likely to see them, but that same immersion isn't necessary for the foliage that peaks through the ceiling that users will never interact with.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CastShadow-Enabled.jpg" alt="Foliage casting shadows." width="100%"/>
|
||||
<figcaption>`Class.BasePart.CastShadow` = Enabled</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CastShadow-Disabled.jpg" alt="Foliage not casting shadows." width="100%"/>
|
||||
<figcaption>`Class.BasePart.CastShadow` = Disabled</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### DoubleSided
|
||||
|
||||
The `Class.MeshPart.DoubleSided` property determines whether to render both faces or polygons in the mesh. When you enable this property for a planar `Class.MeshPart`, such as leaves, hair, or cloth cards, the Roblox Engine renders both faces or polygons in the mesh to allow users to see the fullness of the object no matter what angle they are looking at the object. For example, the following tree leaves are single-sided planar meshes, and when you enable this property, any of the leaves that face away from the camera become visible to the user.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/DoubleSided-Enabled.png" alt="A tree with all planar meshes facing the camera." width="100%"/>
|
||||
<figcaption>`Class.MeshPart.DoubleSided` = Enabled</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/DoubleSided-Disabled.png" alt="A tree with less leaves because all planar meshes facing away from the camera are disabled." width="100%"/>
|
||||
<figcaption>`Class.MeshPart.DoubleSided` = Disabled</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
This property is useful for adding realism to your environment, but it comes at a performance cost because the engine needs to render the object's polygons twice: once for the direction that faces the user's camera, and again for any meshes that face away from the user's camera. To alleviate some of this impact on performance, the final sample laser tag environment only enables this property for foliage because it has a strong visual impact for realism in the 3D space.
|
||||
|
||||
### CollisionFidelity
|
||||
|
||||
The `Class.MeshPart.CollisionFidelity` property determines how closely the physical hitbox of a mesh or union matches its visual representation. By default, this setting renders a hitbox that's roughly the same as the polygonal shape of the mesh, meaning that where users collide with the object is similar to the actual geometry of the mesh. For example, the castle mesh in the following images has a default hitbox that almost matches the shape of its geometry.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/physics/collisions/Collision-Fidelity-MeshPart.jpg" alt="A grey castle mesh." width="100%"/>
|
||||
<figcaption>Original Mesh</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/physics/collisions/Collision-Fidelity-Default.jpg" alt="The same castle mesh with a colorful hitbox that shows where users can collide with the castle." width="100%"/>
|
||||
<figcaption>Default</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
You can set this property to a different value to reduce the precision of a mesh's hitbox. For example, when you set this property to **Hull**, the Roblox Engine significantly reduces the vertices of the mesh's hitbox, and when you set this property to **Box**, the engine reduces the mesh's hitbox to a cube that surrounds the mesh. In addition, if you want to increase the precision of a mesh's hitbox, you can set this property to **PreciseConvexDecomposition** to increase the amount of vertices in the hitbox.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../../../assets/physics/collisions/Collision-Fidelity-Box.jpg" alt="The same castle mesh with cube hitbox that shows where users can collide with the castle." width="100%"/>
|
||||
<figcaption>Box</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/physics/collisions/Collision-Fidelity-Hull.jpg" alt="The same castle mesh with a cylindrical hitbox that shows where users can collide with the castle." width="100%"/>
|
||||
<figcaption>Hull</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/physics/collisions/Collision-Fidelity-Precise.jpg" alt="The same castle mesh with a colorful hitbox that shows where users can collide with the castle. This version shows more colorful faces than the default hitbox." width="100%"/>
|
||||
<figcaption>PreciseConvexDecomposition</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Similar to `Class.BasePart.CastShadow`, the more vertices the Roblox Engine needs to render, the higher the performance cost. For this reason, it's important to consider how you want users to interact with each mesh to see if you can reduce the amount of vertices the engine needs to render for the mesh's hitbox. In cases where it isn't necessary for a hitbox to be precise, set this property to either **Box** or **Hull**.
|
||||
|
||||
To demonstrate this concept, reference the following images that display the hitbox of every mesh of the wall model that provides a rise in elevation between the first and second floor of the final sample laser tag environment. The default hitbox for the main wall has a lot of unnecessary geometry when it only needs to provide a surface to prevent the user from moving in that direction. If you set the property to **Box** for this mesh, you can remove this redundant geometry while keeping the purpose of the hitbox intact.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-WallMesh.jpg" alt="A wall model that provides a rise in elevation." width="100%"/>
|
||||
<figcaption>Original Mesh</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-Default.jpg" alt="The same wall model with a colorful hitbox that shows where users can collide with the wall." width="100%"/>
|
||||
<figcaption>Default</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-Box.jpg" alt="The same wall model with a colorful hitbox for the trim, but only a box hitbox for the wall mesh." width="100%"/>
|
||||
<figcaption>Box</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
As you're making these decisions, it's important to consider how changing the default value may negatively affect users as they're navigating your environment. For example, if you set the property to **Box** for the trim meshes of the wall model, users would collide with the hitbox if they try to jump near the wall. Instead, set these meshes to **Hull** so you reduce its vertex count while keeping the hitbox closer to the shape of the mesh's geometry.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-BoxTop.jpg" alt="A wall model with a box hitbox for the top trip. Because the box sticks out, users can unintentionally collide with the top trim." width="100%"/>
|
||||
<figcaption>Box</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-Hull.jpg" alt="The same wall model with a hull hitbox for the top trip. Because hull hitbox conforms much closer to the top of the model, allowing users to avoid colliding with the trim." width="100%"/>
|
||||
<figcaption>Hull</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Note that it can be important to have a hitbox that precisely conforms to the shape of your mesh, especially when you need to control exactly how users collide with its shape. For example, in the final sample laser tag environment, it's important that users only collide with the edges of the doorway and not the doorway itself, otherwise they would never be able to enter or exit a combat pocket.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/CollisionFidelity-Doorway.png" alt="A doorway model with optimized hitbox meshes." width="50%"/>
|
||||
|
||||
<Alert severity="success">
|
||||
To view collision fidelity in Studio, toggle on **Collision fidelity** from the [Visualization Options](../../../studio/ui-overview.md#visualization-options) widget in the upper‑right corner of the 3D viewport.
|
||||
</Alert>
|
||||
|
||||
### RenderFidelity
|
||||
|
||||
The `Class.MeshPart.RenderFidelity` property determines the mesh's level of detail that displays to the user. When you set this property to `Enum.RenderFidelity.Automatic`, the Roblox Engine reduces the mesh's fidelity the further the user is from the mesh, and when you set this property to `Enum.RenderFidelity.Precise`, every vertex of your mesh renders exactly as you intend no matter the distance between them.
|
||||
|
||||
The more objects the engine must render precisely to the user from any distance, especially if they have a high vertex count, the higher the performance cost. To alleviate some of this impact on performance, set this property to `Enum.RenderFidelity.Performance` for objects with geometric complexity, such as foliage in the sample asset library, because they don't need to display in their entirety from a distance for any gameplay requirement. This reduces your experience's polygon count without yielding overall visual quality.
|
||||
|
||||
## Convert assets into packages
|
||||
|
||||
Now that all of your modular assets are in Studio with rendering parameters that are set up to retain their high visual quality for all users, it's time to convert them into packages. It's important to convert your assets into packages because it allows you to reuse them repeatedly throughout your current experience and other projects. In addition, you can easily make edits to a package that instantly populate to all of its instances, saving you a lot of time in the iteration process.
|
||||
|
||||
Because your `Class.SurfaceAppearance` objects are already packages, when you convert your `Class.Model` objects into packages, they become nested packages. Nested packages allow you to maintain complex hierarchies of child objects that you can modify independent of the `Class.Model` object package. This allows you greater control over the individual components of your assets.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/ConvertingAssets-Intro.png" alt="" width="30%"/>
|
||||
|
||||
To convert your modular assets and props into packages:
|
||||
|
||||
1. In the **Explorer** window, right-click a modular asset or a prop, then select **Convert to Package** from the contextual menu. The **Convert to Package** dialog displays.
|
||||
2. Fill out the **Title** and **Description** fields, set the ownership to yourself or a group, then click the **Submit** button. Once complete, a chain link symbol displays over the model's icon to identify it as a package.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section3/ConvertingAssets-2.png" alt="The Model icon with a chain link symbol." width="30%"/>
|
||||
|
||||
3. Repeat this process for each modular asset and prop.
|
||||
|
||||
Once you are happy with the overall layout of your asset library, you can move on to decorating the environment with your newly polished assets, and configuring additional elements of the 3D space to bring your world to life.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,184 @@
|
|||
---
|
||||
title: Develop polished assets
|
||||
description: Explains the high-level concepts regarding how to design and develop polished assets.
|
||||
next: /tutorials/curriculums/environmental-art/assemble-an-asset-library
|
||||
prev: /tutorials/curriculums/environmental-art/greybox-your-environment
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/JiXnyF0fbrU?si=PGiGEjHqosGplmYm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Developing polished assets** is the process of planning and creating high-quality assets to replace or convert the greybox environment and meet your experience's aesthetic goals and design requirements. After you have the basis for how you want to lay out your assets in the 3D space from the greybox process, it's much easier to visualize what assets are necessary to bring your environment to life.
|
||||
|
||||
Using the [Environment Art Asset Library](https://www.roblox.com/library/14447738661/Environment-Art-Asset-Library) as a reference, this section of the environmental art curriculum shows you how to prepare what you need to turn your basic greybox layout into a futuristic laser tag environment, including guidance on:
|
||||
|
||||
- Choosing an art style to influence every decision you make for your assets in the design process.
|
||||
- Designing tileable textures, trim sheets, a modular kit, and props that are cohesive with one another, and in accordance with your art style.
|
||||
|
||||
After you complete this section, you will learn how to pull all of your polished assets into the 3D space with additional terrain, light sources, and special effects to add character and storytelling elements to your environment.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/Overview.png" alt="A polished group of assets that make up an entryway." width="100%"/>
|
||||
|
||||
<Alert severity="info">
|
||||
It's important to note the process between polishing assets and constructing your environment hardly ever looks like a straight line from your original idea to the final art. Iteration between each step and across steps is normal and almost always necessary in the development process.
|
||||
</Alert>
|
||||
|
||||
## Choose an art style
|
||||
|
||||
Choosing an art style at the start of your design process allows you to have a main source of truth for the aesthetic treatment of your assets. You can treat it as your visual ground rules, constantly referencing it as you design your assets to ensure they always inform users of what you want your environment to communicate. An effective art style provides users information about the world they're playing in, such as its climate, rich history, or relationship to technology.
|
||||
|
||||
Many developers choose to create a mood or theme board, then add as many pictures as you need until you feel that you have a visual representation of what you want your world to look like. For example, the final sample laser tag environment's art style references the following images to pull references for the focal elements of the 3D space, such as lush foliage, rounded frames, rectangular concrete forms, and stylistically uniform metal adornments. Although users may never see your reference images, they will feel the impact of their influence in the cohesiveness of your world's identity.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/Art-Style.png" alt="A collage of reference images for the sample laser tag experience." width="80%"/>
|
||||
|
||||
## Design textures
|
||||
|
||||
Now that you have an art style for your experience, it's time to review your layout and figure out what textures you need to create materials for the 3D space. Environments often require textures that can cover large surfaces, as well as textures that can provide a level of detail to your assets without adding additional geometry. For example, your greybox environment needs textures that can cover the outdoor space, such as moss, flowers and stones, as well as textures to provide details to the combat pockets, such as panels and bolts to frame doorways.
|
||||
|
||||
There are two high-level texturing methods that you can use to meet these requirements: tileable textures and trim sheets. The following sections provide information and guidance on both texture methods, including what to consider as you find textures on the [Creator Store](../../../production/creator-store.md), or design them in third-party tools.
|
||||
|
||||
### Tileable textures
|
||||
|
||||
**Tileable textures** are textures that tile both on the X **and** Y axis, and they allow you to cover large surfaces in your environment, such as floors, walls, and terrain. This type of texture requires minimal manual adjustments after you apply them to block `Class.Part|Parts` because blocks have flat surfaces, meaning you don't need to account for how the texture might deform on more complex geometry. In addition, you can import tileable textures `Class.MaterialVariant` objects within the `Class.MaterialService` to apply to your terrain, which is a process you will learn in [Create custom materials](./assemble-an-asset-library.md#create-custom-materials) in the next section of this tutorial.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-Part.png" alt="A top-down view of a block part with a tileable moss with flowers material applied." width="100%"/>
|
||||
|
||||
The most fundamental rule for tileable textures is that they **cannot have a seam**, otherwise where the texture begins and ends on a surface is noticeable to users, which can break their sense of immersion in the experience. It's for this reason that tileable textures work well for natural materials, such as grass and stone, which are seamless by nature in the real world. In fact, the laser tag sample experience uses the following four organic tileable textures to represent natural materials for the outdoor space, which you can use in the [world building](./construct-your-world.md) section of this tutorial.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-Moss.png" alt="A sphere part with a tileable moss material applied." width="100%"/>
|
||||
<figcaption>Moss</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-MossFlowers.png" alt="A sphere part with a tileable moss with flowers material applied." width="100%"/>
|
||||
<figcaption>MossFlowers</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-MossStones.png" alt="A sphere part with a tileable moss with stones material applied." width="100%"/>
|
||||
<figcaption>MossStones</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-Stones.png" alt="A sphere part with a tileable stones material applied." width="100%"/>
|
||||
<figcaption>Stones</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Notice how all of these sample tileable textures have equal visible distribution of their moss, flower, and rock elements. If you were to include any distinctive elements, such as a massive rock or a patch of flowers, it would attract user attention to the fact that the texture is repeating. For example, the first image in the following two images has a noticeable patch of dirt that pulls the eye to the texture's repetition. The second image makes this patch much smaller, which balances the texture's elements, and makes its repetition much less noticeable.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-WithElement.jpg" alt="A tileable rock texture with a distinct patch of rocks highlighted to show that the repetition of the distinct element is noticeable." width="100%"/>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TileableTextures-NoElement.jpg" alt="A tileable rock texture with no distinct elements to show that this technique makes the repetition of elements not noticeable." width="100%"/>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
If you decide to design your own tileable textures in third-party modeling tools like [Substance Designer](https://www.adobe.com/products/substance3d-designer) or [Blender](https://www.blender.org/), keep the following in mind:
|
||||
|
||||
- You can create textures as large as 1024x1024, but the closer you get to this maximum size, the higher the performance cost.
|
||||
- Create equal visible distribution so that no one element is more distinguishable than others.
|
||||
- Even if a tileable texture is technically seamless, review the overall image to ensure it doesn't have an element that is so pronounced that the texture's repetition is noticeable.
|
||||
|
||||
The previous point is near impossible to remove entirely, but you can set Studio material to tile organically, or add in additional decal overlays to hide the repetition effectively. For more information on these techniques, see [Assemble an asset library - Creating custom materials](./assemble-an-asset-library.md#create-custom-materials) and [Assemble modular environments - Reduce visible repetition](../../use-case-tutorials/modeling/assemble-modular-environments.md#reduce-visible-repetition), respectively.
|
||||
|
||||
### Trim sheets
|
||||
|
||||
**Trim sheets** are textures that tile on either the X **or** Y axis, and they allow you to add significantly more visual complexity to your experiences without having to import additional textures, saving you a negative impact on memory. Each row or column of a trim sheet has a unique visual appearance, giving you many different surface treatments to choose from when you're mapping UV data to a mesh. For example, the door frame and ceiling assets in the following two images use different layers of the same trim sheet to add detail work to the space.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Doorway.jpg" alt="An doorway with trim sheet textures applied." width="100%"/>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Ceiling.jpg" alt="A group of ceiling tiles with trim sheet textures applied." width="100%"/>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
The most fundamental rule for trim sheets is to avoid contextual details that you can only apply to a single object. This is because trim sheets must have use for many types of objects in your world, and highly specific details are also distinguishable to users as they repeat in the 3D space. For example, in the following image from the [Mystery of Duvall showcase](../../../resources/the-mystery-of-duvall-drive/materialize-the-world.md#surface-appearance-and-trim-maps), the left-side furniture set's trim sheet includes more stain detail than the right-side furniture set's trim sheet. Notice how the extra stain detail makes its repetition prominent in comparison to the furniture set on the right.
|
||||
|
||||
<img src="../../../assets/resources/mystery-of-duvall-drive/materializing-the-world/furniture-set-stain.png" alt="The same furniture set has different trim sheets applied. The furniture set on the left with more distict stain elements has noticeable repetition." width="100%"/>
|
||||
|
||||
Following this fundamental rule, the final sample laser tag environment uses the following trim sheet texture maps with six rows of simple detail work to add visual interest and cohesion to its modular kit and props. You can use this <a href="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheetTextureMaps.zip" download>trim sheet</a> for UV unwrapping in third-party modeling tools, then utilize its texture maps in a `Class.SurfaceAppearance` object when you assemble your asset library. For more information on what each of these texture maps provide to meshes, see [PBR textures - Texture maps](../../../art/modeling/surface-appearance.md#texture-maps).
|
||||
|
||||
<Alert severity="info">
|
||||
This trim sheet file is only useful for UV unwrapping processes in third-party modeling tools. If you try to upload its texture maps to a `Class.SurfaceAppearance` object without completing a UV unwrapping process, there isn't any data for Studio to map to the texture itself.
|
||||
</Alert>
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/Trimsheet.png" alt="The sample laser tag experience's trim sheet." width="50%"/>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Albedo.png" alt="The sample laser tag experience's trim sheet's albedo texture map." width="100%"/>
|
||||
<figcaption>Albedo</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Normal.png" alt="The sample laser tag experience's trim sheet's normal texture map." width="100%"/>
|
||||
<figcaption>Normal</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Roughness.png" alt="The sample laser tag experience's trim sheet's roughness texture map." width="100%"/>
|
||||
<figcaption>Roughness</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/TrimSheets-Metalness.png" alt="The sample laser tag experience's trim sheet's metalness texture map." width="100%"/>
|
||||
<figcaption>Metalness</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
If you decide to design your own trim sheets in third-party modeling tools like [Substance Designer](https://www.adobe.com/products/substance3d-designer), [Blender](https://www.blender.org/), or [ZBrush](https://www.maxon.net/en/zbrush), keep the following in mind:
|
||||
|
||||
- You can create textures as large as 1024x1024, but the closer you get to this maximum size, the higher the performance cost.
|
||||
- The trim sheet must be equal in length both along the X and Y axis.
|
||||
- You can make different design choices to include more contextual details, but once the details are on the trim sheet, you cannot easily hide them.
|
||||
- Export your trim sheet's texture maps in a location that is easy to reference when you're creating an asset library.
|
||||
|
||||
For more information on this technique, see [Building architecture - Creating trim sheets](../../../resources/beyond-the-dark/building-architecture.md#create-trim-sheets).
|
||||
|
||||
## Design modular kits
|
||||
|
||||
Modular kits are sets of assets that seamlessly snap together to create variations of a larger complex object. Designing and using modular kits as a part of your development process is useful because it means you don't need to manually create each individual asset in your experience. Instead, you only need to create a few assets that you can reuse and customize to create variety throughout the scene.
|
||||
|
||||
Not only does this process significantly speed up how quickly you can replace or convert the greybox environment, but when you UV wrap a trim sheet onto the meshes in your modular kit for detail work, it can also help each distinct object throughout the experience feel cohesive. For example, the final sample laser tag environment utilizes a single **SA_EC_Trim_Metal_A** trimsheet to UV wrap onto all of its modular assets that create the overall building where most of the gameplay takes place, including assets that accommodate a rise in elevation between the first and second floor.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/DesigningModularKits-ModularKit.jpg" alt="The sample laser tag experience's modular kit." width="100%"/>
|
||||
|
||||
Each asset in this modular kit has a consistent pivot point location either at the forward-most, lower corner, or in a location that allows them to snap to a logical position on the building in 5 stud increments when you enable grid snapping, such as trim pieces onto walls, or doors into their doorway position. In addition, each asset is at least 5 studs tall and wide, so there is never any clashing geometry, even when you rotate and move the assets. For more information on this concept, see [Assemble modular environments - The importance of consistent pivot point locations](../../use-case-tutorials/modeling/assemble-modular-environments.md#the-importance-of-consistent-pivot-point-locations).
|
||||
|
||||
<video controls src="../../../assets/tutorials/environmental-art-curriculum/Section2/DesigningModularKits-Snapping.mp4" width="100%"></video>
|
||||
|
||||
You can use or modify the [sample modular kit](https://www.roblox.com/library/14447738661/Environment-Art-Asset-Library) for replacing graybox geometry in the [world building](../environmental-art/construct-your-world.md) section of this tutorial. However, if you decide to design your own modular kit in third-party modeling tools like [Blender](https://www.blender.org/) or [Maya](https://www.autodesk.com/products/maya/overview), keep the following in mind:
|
||||
|
||||
- It's of vital importance that modular assets have a consistent pivot location so they can connect together at predetermined, incremental distances relative to one another when you enable grid snapping.
|
||||
- Each asset must have a maximum height and width that is divisible by the smallest height and width in the kit so they can move and rotate in incremental distances. For example, the sample kit's tallest and widest asset is 15 by 5 studs, which is divisible by the smallest asset of 5 by 5 studs.
|
||||
- It's useful to separate larger assets like hallways into separate meshes under a model with careful naming conventions, such as a hallway `Class.Model` with child **UpperTrim**, **LowerTrim**, and **Wall** `Class.MeshPart` objects. This allows you to customize each mesh of the overall model, such as modifying their individual material, collision, and rendering parameters, or removing unnecessary geometry according to where the model is within the experience.
|
||||
|
||||
No matter what modular kit you use, it's important to test often and look at your assets from multiple perspectives to see if there is any clashing geometry. If there is, it often means that one of the asset's pivot point locations isn't relatively consistent with the other.
|
||||
|
||||
## Design props
|
||||
|
||||
Props are non-modular assets that enhance an environment's level of visual storytelling while providing users important context about the world they're in. For example, if users are exploring a cave and see props like glowing orbs and skeletons, they can infer that their environment is both magical and dangerous, so it might be wise to proceed with caution.
|
||||
|
||||
Props are most effective when they connect to larger themes in your experience's art style. For example, the final sample laser tag environment utilizes props to provide users context regarding the environment's relationship to technology and nature. In particular, the props kit includes clean, high-tech panels, crates, fire extinguishers, and security cameras alongside large rocks and a variety of plants, informing users that the world values technological advances, but not at the expense of the earth.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/DesigningProps-PropsKit.png" alt="The sample laser tag experience's prop kit." width="100%"/>
|
||||
|
||||
Unlike modular assets, props don't need to have consistent pivot point locations because they don't need to snap together to create a larger complex object. However, they do need to have logical pivot point locations according to where you need to place them in the 3D space. For example, in the following image, the fire extinguisher prop has a pivot point location on the back of the object so it can snap onto walls, and the crate has a pivot point location on the bottom of the object so it can snap onto floors. This allows for universal contextual placement, no matter the respective wall or floor.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section2/DesigningProps-Scale.jpg" alt="" width="100%"/>
|
||||
|
||||
You can use or modify props from the [Environment Art Asset Library](https://www.roblox.com/library/14447738661/Environment-Art-Asset-Library) in the world building section of this tutorial. However, if you decide to design your own props in third-party modeling tools like [Blender](https://www.blender.org/) or [Maya](https://www.autodesk.com/products/maya/overview), keep the following in mind:
|
||||
|
||||
- Users often see props from multiple angles, so it's important to include enough detail for users to understand what the prop is, but not too much detail so that it impacts performance. A good rule is to only include enough geometry that users can tell what a prop is from its silhouette. For more information on this concept, see [Developing a moving world - Setting up the eye of the storm](../../../resources/the-mystery-of-duvall-drive/develop-a-moving-world.md).
|
||||
- You can use the same trim sheet you used for your modular assets so that you make efficient use of textures that are already within your environment, and so that every asset remains stylistically cohesive.
|
||||
- Distinct visual markers in props are more noticeable the more you reuse them in your environment.
|
||||
|
||||
Once you have all of the high-quality assets you need to replace or convert the greybox environment, you can move on to the next section of the tutorial to learn how to assemble an asset library within your place file that you can use to turn your greybox environment into an aesthetically pleasing setting.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
title: Environmental art curriculum
|
||||
description: Learn how to build 3D environments that enhance your experience's gameplay.
|
||||
next: /tutorials/curriculums/environmental-art/greybox-your-environment
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/nwShvDmFHWc?si=2gk0n6cb9uq-48Ni" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "95%", height: "95%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
|
||||
**Environmental art** is the discipline of creating and configuring 3D environments
|
||||
that embody and facilitate gameplay requirements, immerse users within your experience,
|
||||
and provide contextual information about the world itself.
|
||||
|
||||
You'll learn how to recreate a [high-quality environment](https://www.roblox.com/games/14447845297/Environment-Art-Optimizing)
|
||||
for a first-person shooter laser tag experience by following step-by-step processes and
|
||||
best practices for level design, developing assets, and optimization efforts for memory
|
||||
and performance that you can then implement in your own experiences.
|
||||
|
||||
This course is intended for readers who are familiar
|
||||
with general building concepts and tools in Roblox Studio. If you need help
|
||||
learning how to build an environment, try the [core curriculum](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Greybox your environment</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [add simple shapes to the 3D space](greybox-your-environment.md) in order to figure out how users will
|
||||
experience gameplay before investing time into polishing assets.
|
||||
|
||||
<a href="https://www.roblox.com/games/14447721254/Environment-Art-Greyboxing">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Place File
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Develop polished assets</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn about the concepts behind [designing high-quality assets](develop-polished-assets.md) in third-party modeling tools that replace the greybox environment.
|
||||
|
||||
<a href="https://www.roblox.com/library/14447738661/Environment-Art-Asset-Library">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Asset Library
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Assemble an asset library</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [import and configure your assets](assemble-an-asset-library.md) in a central location within your experience for easy access and reuse.
|
||||
|
||||
<a href="https://www.roblox.com/games/14447787049/Environment-Art-Assembling">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Place File
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Construct your world</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [utilize your asset library](construct-your-world.md), as well as how to add and configure additional elements of the 3D space to bring your world to life.
|
||||
|
||||
<a href="https://www.roblox.com/games/14447826396/Environment-Art-Constructing">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Place File
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 5 - Optimize your experience</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn different strategies to [keep your frame rate and performance levels high](optimize-your-experience.md) across devices, especially those with memory limitations.
|
||||
|
||||
<a href="https://www.roblox.com/games/14447845297/Environment-Art-Optimizing">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Place File
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
title: Optimize your experience
|
||||
description: Explains how to configure your assets and Studio settings to improve frame rate and performance levels.
|
||||
prev: /tutorials/curriculums/environmental-art/construct-your-world
|
||||
---
|
||||
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/vuwn2-atYK8?si=ktbfo_Zx64qIY5AL" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
<br/>
|
||||
|
||||
**Optimizing your experience** is the process of configuring your assets and Studio settings to keep your frame rate and performance levels high for devices with memory and graphics processing unit (GPU) limitations. This process ensures that nearly every user has the same gameplay and visual experience while they're viewing your environment.
|
||||
|
||||
Using the [Environment Art - Optimizing](https://www.roblox.com/games/14447845297/Environment-Art-Optimizing) `.rbxl` file as a reference, this section of the environmental art curriculum shows you how to review and configure your place file for optimal graphics, including guidance on:
|
||||
|
||||
- Reviewing the physics and rendering parameters of each asset to confirm they preserve memory and engine performance.
|
||||
- Culling excess texture, geometry, or transparencies that unnecessarily increase the amount of calculations the Roblox Engine must perform to render your assets.
|
||||
|
||||
While the Roblox Engine handles most optimization work for you, you can assist in these optimization efforts by utilizing the [Microprofiler](../../../performance-optimization/microprofiler/index.md) to see where it takes more time to render specific frames. Using this information, you can make informed decisions about what assets need your attention in regards to their parameters or excess content.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/Overview.jpg" alt="" width="100%"/>
|
||||
|
||||
## Review physics and render parameters
|
||||
|
||||
In [Assemble an asset library](./assemble-an-asset-library.md), you learned how important it is to set physics and rendering parameters that allow your assets to retain their high visual quality across devices with memory and GPU limitations. However, it's common as you construct your environment to adjust these parameters according to an asset's contextual position and purpose within your experience. For example, much of the foliage in the final sample laser tag environment casts shadows despite a performance cost because it adds to the realism of the environment.
|
||||
|
||||
When you modify physics and rendering parameters, it's useful near the end of the development process to review all parameters to see where you can optimize a parameter while maintaining aesthetic goals and gameplay requirements. To illustrate, you can disable `Class.BasePart.CastShadow` property for the foliage near the edges of the gameplay area to save on performance without interfering with a user's gameplay or visual experience.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/ReviewingParameters-Disabled.jpg" alt="An outdoor view of the sample laser tag experience that casts shadows." width="100%"/>
|
||||
<figcaption>`Class.BasePart.CastShadow` = Disabled</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/ReviewingParameters-Enabled.jpg" alt="An outdoor view of the sample laser tag experience that doesn't cast shadows. There is almost no difference in this view, but it improves performance." width="100%"/>
|
||||
<figcaption>`Class.BasePart.CastShadow` = Enabled</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Cull nonessential content
|
||||
|
||||
After you review your physics and rendering parameters, you can review the assets themselves to see where you can cull any nonessential content from the experience that doesn't affect your gameplay, such as identical textures with different asset IDs, complex geometry with many vertices, or transparencies that layer on top of each other depending on the camera view. The following sections detail what you can do to review this content, and why it helps optimization efforts.
|
||||
|
||||
### Remove duplicate textures
|
||||
|
||||
As you transition between developing your assets and constructing your environment, it's common to iterate over meshes or textures as you find what's necessary for your aesthetic goals or gameplay requirements. If you don't convert your assets into [packages](../../../projects/assets/packages.md), when you import these iterations into Studio, you're making unique asset IDs that the Roblox Engine needs to reference as it renders your assets within the environment.
|
||||
|
||||
For example, if you were to import the following two fire hydrant meshes into Studio separately, even if they are exactly the same in appearance, the Roblox Engine treats them as two objects with unique asset IDs. The more unique calls the engine needs to make, the more of an impact on memory and performance. For this reason, it's important to confirm when you're reusing an asset multiple times, each instance of that asset uses the same asset ID so that the engine only needs to make a single call to render it repeatedly.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/RemovingTextures-Firehydrants.jpg" alt="Two of the exact same fire hydrant with unique asset IDs. There is no visual difference but the duplicate asset IDs negatively impacts performance." width="100%"/>
|
||||
|
||||
### Optimize geometry
|
||||
|
||||
If you find that you need to make more adjustments to increase frame rate across devices, it's useful to see where you can optimize your geometry by either:
|
||||
|
||||
- Combining groups of meshes into a single asset.
|
||||
- Decreasing the polygon count of assets with geometric complexity.
|
||||
|
||||
Expanding on this first technique, every unique asset in your experience represents a draw call on the GPU in which it sends a signal to the GPU to call information in order for the Roblox Engine to render the asset correctly. The more unique assets you have, the more draw calls the system needs to make. For this reason, if you have a group of meshes that make up a larger component in your experience, you can group them together in third-party modeling tools to reduce the need for multiple draw calls.
|
||||
|
||||
To illustrate this point, the final sample laser tag environment parents multiple parts and meshes together to create the large towers outside of the building. If you were to combine all of these individual components together, you could make it a single asset with only one asset ID, and reduce the number of draw calls from 8 to 1. However, it's important to note that this technique removes your ability to freely change the visual and physical characteristics of each component, such as its position or material.
|
||||
|
||||
For example, in the following image, the left tower remains multiple assets under a `Class.Model` object, and the right tower is a single asset. You can modify each component of the left tower individually, but when you modify the right tower, such as changing its color to black, it affects the entirety of the object. This is why it's important to only consider this technique near the end of your environment's development when you know where you can improve performance without affecting your aesthetic goals.
|
||||
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/OptimizingGeo-Towers.jpg" alt="A front view of the two towers. The tower on the left includes multiple assets under a single model, while the tower on the right decreases the amount of assets by removing all texture objects." width="100%"/>
|
||||
|
||||
Expanding on the second technique, assets with geometric complexity have more polygons, meaning they have more vertices that the engine needs to calculate as it renders their visual appearance. This means that assets with less complexity and fidelity are less costly to render, leading to an improvement in both performance and memory.
|
||||
|
||||
<Alert severity="warning">
|
||||
If you choose to utilize this second technique, after you simplify your geometry in third-party tooling, it's important to remember to update your packages with the new import instead of creating a new asset ID, otherwise the Roblox Engine needs to make two separate calls for each asset ID.
|
||||
</Alert>
|
||||
|
||||
### Delete layered transparencies
|
||||
|
||||
To provide a sense of realism to the environment, the final sample laser tag environment includes many meshes with varying levels of transparency, such as the foliage in the outdoor space, glass on the futuristic signage or planter railing. When the camera views multiple semi-transparent objects that are in front or behind each other, the Roblox Engine must render the overlapping pixels multiple times to account for the
|
||||
transparent areas. This process is called high transparency overdraw, and it comes at a significant impact to performance.
|
||||
|
||||
For example, consider the following view of a planter in the sample environment. The engine must render the transparent areas of the leaves between the plant closest to the camera to the plant closest to the outdoor area in layers, equating to hundreds of thousands of overdrawn pixels. To alleviate some of this impact, it's important to review the layout of all semi-transparent objects in your environment, and ensure there aren't too many places where there are many layers of overlap, especially in large areas of the screen.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/LayeredTransparencies-Left.jpg" alt="A Rthro avatar facing a planter with multiple plants with transparency between the leaves." width="100%"/>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/environmental-art-curriculum/Section5/LayeredTransparencies-Right.jpg" alt="A side view of the Rthro avatar facing a planter with multiple plants with transparency between the leaves, and example layers of overdrawn pixels are highlighted to show where there is overdrawn in the environment." width="100%"/>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
When you finish reviewing all of your content to ensure it's optimal across devices, your experience is now ready for publication!
|
||||
|
||||
<Alert severity="info">
|
||||
We're interested in hearing from you about your experience following the Environmental Art Curriculum. If you have any questions, concerns, or additional feedback on the process, please comment on our [Environmental Art Curriculum Q&A](https://devforum.roblox.com/t/feedback-on-environmental-art-curriculum/2592218).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
---
|
||||
title: Add rounds
|
||||
description: Explains how to implement round-based behavior.
|
||||
next: /tutorials/curriculums/gameplay-scripting/implement-blasters
|
||||
prev: /tutorials/curriculums/gameplay-scripting/spawn-respawn
|
||||
---
|
||||
|
||||
**Adding rounds** lets you structure gameplay into phases with a clear start and finish point so that players can measure their progress and have a periodic opportunity for an equal playing field. This is particularly important for team-based gameplay because it offers players the chance to switch up their play style depending on who is on their team during that round.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you how to use and customize Roblox's built-in features to structure each round, including scripting guidance on:
|
||||
|
||||
- Beginning a round by resetting individual and team points, then spawning players to their team spawn zones.
|
||||
- Customizing variables that set the objective for the round at the top of each player's screen.
|
||||
- Tracking player point contributions for their team score.
|
||||
- Triggering unique UI screens depending on if the player's team won or lost the round.
|
||||
- Ending a round by disconnecting players and spawning them in the neutral lobby.
|
||||
|
||||
After you complete this section, you will learn how to implement blaster behavior that is both accurate and satisfying to players.
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Adding-Rounds/Rounds-Diagram.png" width="100%" alt="" />
|
||||
|
||||
## Start loop
|
||||
|
||||
**ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** handles most of the logic for implementing rounds, and it starts by calling the `startRoundLoopAsync()` function to mark the beginning of a round loop. While players join the lobby and wait to sort into a team, `startRoundLoopAsync()` calls the `resetScores()` function in **ServerScriptService** ⟩ **Gameplay** ⟩ **Scoring** to reset both the leaderboard and team points.
|
||||
|
||||
```lua title="Scoring"
|
||||
function Scoring.resetScores()
|
||||
for _, player in Players:GetPlayers() do
|
||||
player.leaderstats.Points.Value = 0
|
||||
end
|
||||
|
||||
for _, team in Teams:GetTeams() do
|
||||
team:SetAttribute(GuiAttribute.teamPoints, 0)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now that everyone is starting at zero points, `startRoundLoopAsync()` then sets the **Neutral** spawn location's `Class.SpawnLocation.Neutral|Neutral` property to **false** so that only players with the same `Class.Team.Color` property as the spawn location's `Class.SpawnLocation.TeamColor|TeamColor` property can spawn there. Because the spawn location's `Class.SpawnLocation.TeamColor|TeamColor` property is set to **white** instead of the sample's mint or carnation pink teams, this configuration prevents all players from spawning or respawning there while a round is active.
|
||||
|
||||
For players who are currently in the lobby, `startRoundLoopAsync()` passes all players currently within the experience to the `spawnPlayersInMap` function in **ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** ⟩ **spawnPlayersInMap** to sort and balance everyone into a team with approximately the same number of players.
|
||||
|
||||
For any new players that join the experience after the lobby group was sorts into a team, `startRoundLoopAsync()` listens for the `Players.PlayerAdded:Connect` event, then calls the `spawnPlayersInMap` function again to add them to the team with the least amount of players. For more information on this process, see [Configure Spawn Locations](spawn-respawn.md#configure-spawn-locations) from the previous **Spawning and Respawning** section of the tutorial.
|
||||
|
||||
```lua title="Rounds"
|
||||
-- Spawn all players in the map
|
||||
neutralSpawn.Neutral = false
|
||||
spawnPlayersInMap(Players:GetPlayers())
|
||||
|
||||
-- Spawn new players in the map when they join
|
||||
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
|
||||
spawnPlayersInMap({ player })
|
||||
end)
|
||||
```
|
||||
|
||||
## Set objective
|
||||
|
||||
Now that each player is in the arena with their teammates, the experience needs to provide instructions for what to do to be successful within the round. The sample laser tag experience tackles this requirement by providing an objective prompt at the top of each player's screen with clear guidance on what the team needs to do to win.
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Objective-UI.jpg" width="100%" alt="" />
|
||||
|
||||
While you can learn more about how to configure and display the [Objective UI](../user-interface-design/implement-designs-in-studio.md#objective-ui) component in the UI Curriculum, this section focuses on how to implement the objective goal as the round begins, starting with how to set the amount of points each team needs to complete the round.
|
||||
|
||||
Even though the objective prompt at runtime informs players they need to score three points to win, if you examine the prompt in **StarterGui** ⟩ **HUDGui**, you can see that it instead contains a configurable "`%d`" for the point value.
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Adding-Rounds/Placeholder-Prompt.jpg" width="100%" alt="" />
|
||||
|
||||
"`%d`" is a placeholder string that you can increase or decrease at any time to meet your own gameplay requirements by updating the `TEAM_SCORE_LIMIT` variable in **ReplicatedStorage** ⟩ **TEAM_SCORE_LIMIT**. For example, if you were to set this number to an excessively high `200`, the prompt and team point counter would update accordingly.
|
||||
|
||||
```lua title="TEAM_SCORE_LIMIT"
|
||||
local TEAM_SCORE_LIMIT = 200 -- updated line, be sure to change back
|
||||
|
||||
return TEAM_SCORE_LIMIT
|
||||
```
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Adding-Rounds/Large-Prompt.jpg" width="100%" alt="" />
|
||||
|
||||
This simple variable update works on runtime because as the round starts, **ReplicatedStorage** ⟩ **HUDGuiSetup** ⟩ **SetObjective** requires the `TEAM_SCORE_LIMIT` module script so that it can swap the placeholder string in the UI Objective's `Class.TextLabel` object.
|
||||
|
||||
```lua title="TEAM_SCORE_LIMIT"
|
||||
local TEAM_SCORE_LIMIT = require(ReplicatedStorage.TEAM_SCORE_LIMIT)
|
||||
|
||||
local function setObjective(gui: ScreenGui)
|
||||
local bodyTextLabel = gui.Objective.ObjectiveDisplay.Body.BodyTextLabel
|
||||
bodyTextLabel.Text = bodyTextLabel.Text:format(TEAM_SCORE_LIMIT)
|
||||
end
|
||||
```
|
||||
|
||||
## Track points
|
||||
|
||||
Now that players have a goal for the round, the experience needs to keep track of each team's points until they meet their objective. While the `Class.Teams` service's default behavior automatically groups each player underneath their team and adds up each player's contributions to their team score, it's important to store and monitor points in a separate location for round-based gameplay because if a player scores then leaves before the round is over, their contribution is deducted from the leaderboard as soon as they disconnect from the experience.
|
||||
|
||||
To ensure this doesn't happen and every contribution toward the team goal is preserved, **ReplicatedStorage** ⟩ **HUDGuiSetup** ⟩ **StartSyncingTeamPoints** stores all points separately under the `teamPoints` attribute in `Class.Teams` service. As `teamPoints` increments, this module script calls function `startSyncingTeamPoints` to find the team counter `Class.GuiObjects` within the Objective UI component.
|
||||
|
||||
When it locates **TeamACounter** and **TeamBCounter**, it gets their `teamColor` attribute, which correlates with the team spawn zones: TeamACounter displays the green team's points, and TeamBCounter tracks the pink team's points.
|
||||
|
||||
```lua title="StartSyncingTeamPoints"
|
||||
local function startSyncingTeamPoints(gui: ScreenGui)
|
||||
for _, teamPointCounter in gui.Objective.TeamPointCounter:GetChildren() do
|
||||
if not teamPointCounter:IsA("GuiObject") then
|
||||
continue
|
||||
end
|
||||
|
||||
local iconTeamColor = teamPointCounter:GetAttribute(GuiAttribute.teamColor)
|
||||
```
|
||||
|
||||
The module script then calls its `getTeamFromTeamColor` function to validate that the TeamACounter's **mint** `teamColor` attribute and the TeamBCounter's **carnation pink** `teamColor` attribute both match the corresponding `Class.Team.Color` properties underneath the `Class.Teams` service. If so, it returns both of the teams.
|
||||
|
||||
```lua title="StartSyncingTeamPoints"
|
||||
local function getTeamFromTeamColor(teamColor: Color3): Team?
|
||||
for _, team in Teams:GetTeams() do
|
||||
if team.TeamColor == teamColor then
|
||||
return team
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
```
|
||||
|
||||
When this occurs, `startSyncingTeamPoints` sets both team counters' `Class.TextLabel` objects to their corresponding `teamPoints` values, and continues to update them whenever a player scores a point by tagging another player out on the opposite team.
|
||||
|
||||
```lua title="StartSyncingTeamPoints"
|
||||
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
|
||||
|
||||
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
|
||||
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
|
||||
```
|
||||
|
||||
Everything in this section so far has focused on how to track points on the player's screen, but it's important to review the logic that handles tracking points on the server so it knows when a team meets the objective goal and wins the round. If you revisit **ServerScriptService** ⟩ **Gameplay** ⟩ **Scoring**, you can see that the module script starts by creating a bindable event, which will fire each time a player scores a point.
|
||||
|
||||
```lua title="Scoring"
|
||||
local teamScoreChangedBindable = Instance.new("BindableEvent")
|
||||
|
||||
local Scoring = {
|
||||
teamScoreChanged = teamScoreChangedBindable.Event,
|
||||
}
|
||||
```
|
||||
|
||||
It then calls the `incrementScore` function, which performs the following actions:
|
||||
|
||||
- Grabs the player's team and their current teamPoints value on the `Class.Team` object in the `Class.Teams` service, then adds one.
|
||||
- Grabs the player's individual score on the leaderboard, and adds one.
|
||||
- Fires the previously mentioned bindable event with both the player's team and their score.
|
||||
|
||||
This process effectively keeps both the client and the server aligned regarding both players' individual scores and their team scores.
|
||||
|
||||
```lua title="Scoring"
|
||||
function Scoring.incrementScore(player: Player, amount: number)
|
||||
local team = player.Team
|
||||
assert(team, `Player {player.Name} must be on a team to score a point, but has no team`)
|
||||
|
||||
local teamPoints = team:GetAttribute(GuiAttribute.teamPoints)
|
||||
teamPoints += amount
|
||||
team:SetAttribute(GuiAttribute.teamPoints, teamPoints)
|
||||
|
||||
local leaderstat = player.leaderstats.Points
|
||||
leaderstat.Value += amount
|
||||
|
||||
teamScoreChangedBindable:Fire(team, teamPoints)
|
||||
end
|
||||
```
|
||||
|
||||
## Display results
|
||||
|
||||
As players tag each other and score points for their team, **ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** checks to see if the team that scored met the round objective. If their team score is lower than the `TEAM_SCORE_LIMIT` variable in **ReplicatedStorage** ⟩ **TEAM_SCORE_LIMIT**, the server continues to wait until one of the teams scores again.
|
||||
|
||||
However, once a team's score reaches the `TEAM_SCORE_LIMIT` variable, the script fires a `roundWinnerRemote` event instance with the player's name and their team.
|
||||
|
||||
```lua title="Rounds"
|
||||
-- Check if the round has finished after each score
|
||||
local team: Team
|
||||
local score: number = 0
|
||||
|
||||
while score < TEAM_SCORE_LIMIT do
|
||||
team, score = Scoring.teamScoreChanged:Wait()
|
||||
end
|
||||
|
||||
-- Display winning team
|
||||
for _, player in Players:GetPlayers() do
|
||||
-- Sending what team the player is on at the end of the round
|
||||
-- because the player's team is about to be removed, so the client
|
||||
-- won't be able to check its own team
|
||||
roundWinnerRemote:FireClient(player, team, player.Team)
|
||||
end
|
||||
```
|
||||
|
||||
The **ReplicatedStorage** ⟩ **RoundResultsGuiSetup** script on each client listens for this `roundWinnerRemote` event instance so it can:
|
||||
|
||||
- Display a unique **StarterGui** ⟩ **RoundResultsGui** UI screen that announces the round's results and if the player was on the winning team.
|
||||
- Play a victory or defeat audio clip.
|
||||
|
||||
For example, if a player is on the team that scored the winning point, they receive multiple forms of feedback on the round results in the form of a UI screen that displays victory text, and an audio clip that plays a joyful sound. Conversely, if a player is not on the team that scored the winning point, they receive a UI screen that displays defeat text, and an audio clip that plays an ominous sound.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<video controls src="../../../assets/tutorials/gameplay-scripting/Adding-Rounds/Victory-UI.mp4" width="100%"></video>
|
||||
<figcaption>Victory feedback</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<video controls src="../../../assets/tutorials/gameplay-scripting/Adding-Rounds/Defeat-UI.mp4" width="100%"></video>
|
||||
<figcaption>Defeat feedback</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
```lua title="RoundResultsGuiSetup"
|
||||
local function onRoundWinner(winner: Team, localTeam: Team?)
|
||||
local victoryDefeatText = "Round ended!"
|
||||
if localTeam then
|
||||
-- If our team won, we'll display Victory! Otherwise display Defeat...
|
||||
local isVictory = winner == localTeam
|
||||
if isVictory then
|
||||
victorySound:Play()
|
||||
victoryDefeatText = VICTORY_TEXT
|
||||
else
|
||||
defeatSound:Play()
|
||||
victoryDefeatText = DEFEAT_TEXT
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Reset teams
|
||||
|
||||
At the same time that **ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** verifies that a team met the round objective and triggers the appropriate UI display for each player, it also transports all players from the arena to the lobby by disconnecting them from the round. This starts the process of formally ending the round and resetting both teams.
|
||||
|
||||
Using the same logic in [Configure Spawn Locations](spawn-respawn.md#configure-spawn-locations), **Rounds** then sets the **Neutral** spawn location's `Class.SpawnLocation.Neutral|Neutral` property to **true** so players can spawn there regardless of their team status. This means the lobby becomes the only location that players can spawn after they are disconnected from the round.
|
||||
|
||||
```lua title="Rounds"
|
||||
-- Send everyone to the lobby
|
||||
playerAddedConnection:Disconnect()
|
||||
neutralSpawn.Neutral = true
|
||||
spawnPlayersInLobby(Players:GetPlayers())
|
||||
```
|
||||
|
||||
After waiting ten seconds for an intermission, the **Rounds** server script then starts the loop again by resetting everyone's scores and sorting them into new teams. The sample repeats this cyclical round process again until there aren't any players within the server.
|
||||
|
||||
Now that players can spawn into the map with their own team and play a full round, the next section teaches you about the scripts behind each blaster's behavior.
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
---
|
||||
title: Create teams
|
||||
description: Explains how separate players into teams as they join an experience.
|
||||
prev: /tutorials/curriculums/gameplay-scripting/
|
||||
next: /tutorials/curriculums/gameplay-scripting/spawn-respawn
|
||||
---
|
||||
|
||||
**Creating teams** lets you sort players into groups that compete to complete a common objective, such as scoring the most points or crossing a finish line before other enemy team members. For a first-person shooter, creating teams is particularly important because it establishes complex, coordinated gameplay strategies beyond the skill set of any individual player.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you how to sort players into teams, including scripting guidance on:
|
||||
|
||||
- Using the `Class.Teams` service's default functionality to assign players into either the green or pink team.
|
||||
- Helping players differentiate between their allies and enemy team members through on-screen and in-experience team indicators.
|
||||
- Enabling forgiving gameplay that doesn't penalize players for blasting their teammates.
|
||||
|
||||
After you complete this section, you will learn about the scripts that allow players to spawn or respawn to a neutral lobby or team spawn zone, customize first-person force field visuals, and handle client state both from the server and the client.
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Intro.png" width="100%" alt="" />
|
||||
|
||||
## Assign team colors
|
||||
|
||||
The sample laser tag experience uses the `Class.Teams` service as a basis for creating two teams because the service offers built-in team sorting behavior that broadly works out-of-the-box. For example, without any additional scripting effort, the service handles actions like:
|
||||
|
||||
- Sorting and balancing players evenly into each team.
|
||||
- Grouping players under their team on the leaderboard.
|
||||
- Tinting player names in the 3D space to their corresponding team color.
|
||||
- Spawning players to distinct spawn locations that only their team can access.
|
||||
|
||||
Because of this default functionality, the sample enables the `Class.Teams` service, then uses two distinct `Class.Team` objects with different `Class.Team.Color` properties values to represent each team: **mint** for the team that assembles on one side of the map, and **carnation pink** for the team that assembles on the opposite side of the map. These specific colors are useful because they are complementary, meaning that they contrast each other well, and they enable players to easily scan their surroundings and orient themselves within the building and to other players.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Green-Team-Players.jpg" alt="" />
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Pink-Team-Players.jpg" alt="" />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Green-Team-Properties.png" alt="" />
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Pink-Team-Properties.png" alt="" />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="info">
|
||||
To learn more about this color theme, see [Select a Color Theme](../user-interface-design/choose-an-art-style.md#select-a-color-theme) from the UI Curriculum.
|
||||
</Alert>
|
||||
|
||||
It's important to note the exact `Datatype.BrickColor` name for the `Class.Team.Color` property because the experience uses this name throughout many scripts in the experience to keep track of players as they sort into teams, increment points as players tag out enemy players during a round, and update custom UI elements. For example, when a round begins, **ReplicatedStorage** ⟩ **HUDGuiSetup** requires `startSyncingTeamColor`,and `startSyncingTeamPoints` which both reference `Class.Team.Color` to perform unique actions for each team.
|
||||
|
||||
<Tabs>
|
||||
<TabItem key = "1" label="startSyncingTeamColor">
|
||||
|
||||
`startSyncingTeamColor` references `Class.Team.Color` so that it can assign the correct color and icon for the player's team indicator at the bottom-left corner of their screen.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Green-Team.jpg" width="100%"/>
|
||||
<figcaption>Green Team</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Pink-Team.jpg" width="100%"/>
|
||||
<figcaption>Pink Team</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
```lua
|
||||
local function setPlayerTeamIcon(gui: ScreenGui)
|
||||
for _, teamColorIcon in gui.PlayerDisplay.TeamIcons:GetChildren() do
|
||||
local iconTeamColor = teamColorIcon:GetAttribute(GuiAttribute.teamColor)
|
||||
teamColorIcon.Visible = localPlayer.TeamColor == iconTeamColor
|
||||
end
|
||||
end
|
||||
|
||||
local function startSyncingTeamColor(gui: ScreenGui)
|
||||
setPlayerTeamIcon(gui)
|
||||
localPlayer:GetPropertyChangedSignal("Team"):Connect(function()
|
||||
setPlayerTeamIcon(gui)
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem key = "2" label="startSyncingTeamPoints">
|
||||
|
||||
`startSyncingTeamPoints` references `Class.Team.Color` so that it can track team points correctly in the objective UI at the top of the screen.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Objective-UI.jpg" width="100%" alt="" />
|
||||
</figure>
|
||||
|
||||
```lua
|
||||
local function getTeamFromTeamColor(teamColor: Color3): Team?
|
||||
for _, team in Teams:GetTeams() do
|
||||
if team.TeamColor == teamColor then
|
||||
return team
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function startSyncingTeamPoints(gui: ScreenGui)
|
||||
for _, teamPointCounter in gui.Objective.TeamPointCounter:GetChildren() do
|
||||
if not teamPointCounter:IsA("GuiObject") then
|
||||
continue
|
||||
end
|
||||
|
||||
local iconTeamColor = teamPointCounter:GetAttribute(GuiAttribute.teamColor)
|
||||
|
||||
local team = getTeamFromTeamColor(iconTeamColor)
|
||||
if not team then
|
||||
warn(`No team found matching the color {iconTeamColor} to sync team points on {teamPointCounter}`)
|
||||
continue
|
||||
end
|
||||
|
||||
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
|
||||
|
||||
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
|
||||
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Display team indicators
|
||||
|
||||
Once a player is sorted into a team, they need to be able to quickly decipher what team they belong to, and differentiate between their allies and enemy team members. This ability is important because the gameplay of a first-person shooter experience requires players to make quick strategic decisions while they're in combat zones so that they don't get tagged out and lose the round.
|
||||
|
||||
To set players up for success, the sample laser tag experience provides multiple team indicators both on-screen and in the 3D space:
|
||||
|
||||
- **Player Indicator** – Custom on-screen UI that displays the player's team color and icon.
|
||||
- **Team Indicator** – Custom in-experience UI that displays the player's team color.
|
||||
- **Leaderboard Indicator** – Default Teams service on-screen UI that groups players underneath their team color.
|
||||
- **Name Tint** – Default Teams service in-experience UI that tints the player's name above their head to their team color.
|
||||
|
||||
<div><b>On-screen Indicators</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Player-Indicator.jpg" alt="" />
|
||||
<figcaption>Player Indicator</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="80%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Leaderboard-Indicator.jpg" alt="" />
|
||||
<figcaption>Leaderboard Indicator</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<div><b>In-Experience Indicators</b></div>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Team-Indicator.jpg" alt="" />
|
||||
<figcaption>Team Indicator</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Name-Tint.jpg" alt="" />
|
||||
<figcaption>Name Tint</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
You don't need to do any additional work for the Leaderboard Indicator or Name Tint outside of assigning team colors from the previous section of the tutorial, but the custom UI team indicators require more programming effort to both display the indicators and occlude enemy indicators when they are behind walls. For a full explanation of the scripts that control the custom UI behavior, as well as how to implement each UI element itself, see [Player Indicator](../user-interface-design/implement-designs-in-studio.md#player-indicator) and [Team Indicator](../user-interface-design/implement-designs-in-studio.md#team-indicator) from the UI Curriculum.
|
||||
|
||||
## Disable friendly fire
|
||||
|
||||
While some first-person shooter experiences penalize players who blast their own teammates, the sample laser tag experience allows for more forgiving gameplay by disabling friendly fire. This design decision allows everyone to only increase their team score, not subtract from it due to gameplay accidents.
|
||||
|
||||
To understand how the sample implements this functionality, examine how **ServerScriptService** ⟩ **LaserBlastHandler** ⟩ **processTaggedPlayers** ⟩ **onPlayerTagged** handles dealing damage to tagged players. When the server detects a collision between a player's blast and another player, it calls the `onPlayerTagged` function to understand which player fired the blast, which player was hit by the blast, and how much damage it should remove from the player's health according to the blaster type.
|
||||
|
||||
```lua
|
||||
local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
|
||||
```
|
||||
|
||||
Before it decreases health, the script first verifies if the tagged player is on the same team as the player who initiated the blast. If they are on the same team, the script ignores the blast data entirely.
|
||||
|
||||
```lua
|
||||
local character = playerTagged.Character
|
||||
local isFriendly = playerBlasted.Team == playerTagged.Team
|
||||
|
||||
-- Disallow friendly fire
|
||||
if isFriendly then
|
||||
return
|
||||
end
|
||||
```
|
||||
|
||||
However, if the tagged player **is** on the enemy team, the player takes the appropriate amount of damage according to the blaster type. In addition, if the player takes enough damage to become tagged out, the script rewards a point to the team of the player that initiated the blast. For more information on how the sample tracks points, see [Add rounds](./add-rounds.md#track-points) later in the tutorial.
|
||||
|
||||
```lua
|
||||
local humanoid = character and character:FindFirstChild("Humanoid")
|
||||
if humanoid and humanoid.Health > 0 then
|
||||
local damage = math.min(damageAmount, humanoid.Health)
|
||||
humanoid:TakeDamage(damage)
|
||||
if humanoid.Health <= 0 then
|
||||
Scoring.incrementScore(playerBlasted, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return onPlayerTagged
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
For more information on what happens when a player fires their blaster, see [Implement blaster behavior](./implement-blasters.md) and [Detect hits](./detect-hits.md) later on in the tutorial.
|
||||
</Alert>
|
||||
|
||||
If you were to test the experience right now, all players would randomly spawn at either spawn zone at opposite ends of the arena regardless of what team they belong to, meaning that each team would spawn right next to each other.
|
||||
|
||||
To address this issue, the following section of the tutorial teaches you about the custom logic behind restricting team spawning to certain spawn locations, handling client state as players leave the spawn area after selecting their blaster, and respawning players back to their spawn zone or lobby area when their health reaches zero.
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
---
|
||||
title: Detect hits
|
||||
description: Explains how hit detection works in a laser tag experience.
|
||||
prev: /tutorials/curriculums/gameplay-scripting/implement-blasters
|
||||
---
|
||||
|
||||
**Detecting hits** is the process of identifying when blasts collide with players, then reducing their health accordingly. At a high-level, you can think of this work as either:
|
||||
|
||||
1. A physically simulated check of whether a projectile struck the target.
|
||||
2. An instantaneous check of whether the blaster was aimed at the target.
|
||||
|
||||
The type of hit detection you use depends on the gameplay requirements of your experience. For example, a physically simulated check is appropriate for a dodgeball experience where balls need to leave the hand at a certain velocity, drop as they move through the air, or change direction from weather conditions. However, an instantaneous check is a better match for a laser tag experience where beams must have a near-infinite velocity and ignore environmental factors like gravity and wind speed.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you about the scripts behind hit detection in the 3D space, including guidance on:
|
||||
|
||||
- Getting the blast direction from the current camera values and the player's blaster type.
|
||||
- Casting rays in a straight path from the blaster as it blasts.
|
||||
- Validating the blast to prevent exploitation of blaster data.
|
||||
- Reducing player health according to the blast damage from each type of blaster and how many rays hit the player.
|
||||
|
||||
After you complete this section, you can explore additional development topics to enhance your gameplay, such as audio, lighting, and special effects.
|
||||
|
||||
## Get blast direction
|
||||
|
||||
After a player blasts their blaster, **ReplicatedStorage** ⟩ **Blaster** ⟩ **attemptBlastClient** ⟩ **blastClient** ⟩ **generateBlastData** calls two functions to start the hit detection process: `rayDirections()` and `rayResults()`.
|
||||
|
||||
```lua title="generateBlastData"
|
||||
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
|
||||
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)
|
||||
```
|
||||
|
||||
The inputs for `rayDirections` are straightforward: the current camera position and rotation values, and the player's blaster type. If the sample laser tag experience only gave players blasters that produce a single laser beam, **ReplicatedStorage** ⟩ **LaserRay** ⟩ **getDirectionsForBlast** would be unnecessary because you could use `currentCamera.CFrame.LookVector` to calculate the direction for the blast.
|
||||
|
||||
However, because the sample provides an additional blaster type that produces several laser beams with a wide, horizontal spread, `getDirectionsForBlast` must calculate the direction for each laser beam of the spread according to their angles within the blaster configuration:
|
||||
|
||||
```lua title="getDirectionsForBlast"
|
||||
if numLasers == 1 then
|
||||
-- For single lasers, they aim straight
|
||||
table.insert(directions, originCFrame.LookVector)
|
||||
elseif numLasers > 1 then
|
||||
-- For multiple lasers, spread them out evenly horizontally
|
||||
-- over an interval laserSpreadDegrees around the center
|
||||
local leftAngleBound = laserSpreadDegrees / 2
|
||||
local rightAngleBound = -leftAngleBound
|
||||
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
|
||||
|
||||
for angle = rightAngleBound, leftAngleBound, degreeInterval do
|
||||
local direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
|
||||
table.insert(directions, direction)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
To demonstrate this concept further, if you were to include a third blaster type with a wide, **vertical** spread, you could create a new blaster attribute, such as `spreadDirection`, then adjust the `Datatype.CFrame` calculation to use a different axis. For example, note the difference in the `direction` calculations in the following script below for this hypothetical third blaster type.
|
||||
|
||||
```lua
|
||||
if numLasers == 1 then
|
||||
table.insert(directions, originCFrame.LookVector)
|
||||
elseif numLasers > 1 then
|
||||
local leftAngleBound = laserSpreadDegrees / 2
|
||||
local rightAngleBound = -leftAngleBound
|
||||
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
|
||||
for angle = rightAngleBound, leftAngleBound, degreeInterval do
|
||||
local direction
|
||||
if spreadDirection == "vertical" then
|
||||
direction = (originCFrame * CFrame.Angles(math.rad(angle), 0, 0)).LookVector
|
||||
else
|
||||
direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
|
||||
end
|
||||
table.insert(directions, direction)
|
||||
end
|
||||
end
|
||||
return directions
|
||||
```
|
||||
|
||||
Ultimately, the `rayDirections()` function returns a table of `Datatype.Vector3|Vectors` that represent the direction of each laser beam. If it's helpful, you can add some logging to get a sense of what this data looks like.
|
||||
|
||||
```lua title="generateBlastData"
|
||||
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
|
||||
for _, direction in rayDirections do -- new line
|
||||
print(direction) -- new line
|
||||
end -- new line
|
||||
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)
|
||||
```
|
||||
|
||||
## Cast rays
|
||||
|
||||
`castLaserRay()`, the second function in **ReplicatedStorage** ⟩ **Blaster** ⟩ **attemptBlastClient** ⟩ **blastClient** ⟩ **generateBlastData**, performs the more complex operations within the script. It begins by specifying parameters so that it can make `Class.Workspace:Raycast()` calls for raycasting purposes. Raycasting is the process of sending out an invisible ray from a `Datatype.Vector3` point in a specific direction with a defined length, then checking its path to see where it intersects with other objects.
|
||||
|
||||
This information is particularly useful for first-person shooter experiences because it allows you to see when and where blasts intersect with players or the environment. For example, the following image demonstrates two rays that are casting parallel to each other. According to their point of origin and direction, Ray A misses the wall and continues until it meets its maximum distance, while Ray B collides with the wall. For more information on this process, see [Raycasting](../../../workspace/raycasting.md).
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Detecting-Hits/Two-Rays.png" width="60%" alt="A diagram where Ray A continues through the wall, and Ray B collides with the wall." />
|
||||
</figure>
|
||||
|
||||
The `castLaserRay()` parameters specify that `Raycast()` calls must consider every part in the workspace **except** the character who blasted. The script then casts a ray for each direction in the `directions` table. If a ray hits something, it generates a `Datatype.RaycastResult`, which has five properties:
|
||||
|
||||
- `Datatype.RaycastResult.Distance|Distance` – The distance between the ray origin and the intersection point.
|
||||
- `Datatype.RaycastResult.Instance|Instance` – The `Class.BasePart` or `Class.Terrain` cell that the ray intersects.
|
||||
- `Datatype.RaycastResult.Material|Material` – The `Enum.Material` at the intersection point.
|
||||
- `Datatype.RaycastResult.Position|Position` – The `Datatype.Vector3` position of the intersection between the ray and the Instance.
|
||||
- `Datatype.RaycastResult.Normal|Normal` – The `Datatype.Vector3` of the normal vector of the face the ray intersects with.
|
||||
|
||||
<Alert severity="warning">
|
||||
By default, `Raycast()` does **not** respect the `Class.BasePart.CanCollide` property. If some parts in your experience are strictly decorative, consider how you want them to behave and see `Datatype.RaycastParams.RespectCanCollide`.
|
||||
</Alert>
|
||||
|
||||
The `Datatype.RaycastResult.Instance|Instance` value is the most critical of these properties for the sample laser tag experience's gameplay because it communicates when rays collide with other players. To retrieve this information, the experience uses the **ReplicatedStorage** ⟩ **LaserRay** ⟩ **castLaserRay** ⟩ **getPlayerFromDescendant** helper function. If it returns `nil`, the instance isn't part of a player, meaning the ray hit an inanimate object within the environment.
|
||||
|
||||
`castLaserRay()` then uses `Datatype.RaycastResult.Position|Position` and `Datatype.RaycastResult.Normal|Normal` to create a new `Datatype.CFrame` that it calls the ray's `destination`. Every ray has a destination, and it's either where the ray hit in the 3D space, or the point at the end of its maximum distance. Depending on how well your players aim, many or most `taggedPlayer` values are `nil`.
|
||||
|
||||
```lua title="castLaserRay"
|
||||
if result then
|
||||
-- The blast hit something, check if it was a player.
|
||||
destination = CFrame.lookAt(result.Position, result.Position + result.Normal)
|
||||
taggedPlayer = getPlayerFromDescendant(result.Instance)
|
||||
else
|
||||
-- The blast didn't hit anything, so its destination is
|
||||
-- the point at its maximum distance.
|
||||
local distantPosition = origin + rayDirection * MAX_DISTANCE
|
||||
destination = CFrame.lookAt(distantPosition, distantPosition - rayDirection)
|
||||
taggedPlayer = nil
|
||||
end
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
`Datatype.RaycastResult.Distance|Distance` isn't particularly interesting in this section's scripts, but you could utilize it in unique ways if you want blasters to inflict more damage at a close range. Similarly, this section's scripts don't consider `Datatype.RaycastResult.Material|Material`, but you could use material types to distinguish between an armored body and a set of weak points during damage calculations.
|
||||
</Alert>
|
||||
|
||||
## Validate the blast
|
||||
|
||||
To prevent cheating, the previous chapter [Implementing Blasters](implement-blasters.md) explains how `blastClient` notifies the server of the blast using a `Class.RemoteEvent` so that it can verify all data that each client sends, such as whether or not they truly tagged another player with their blaster. This ray validation process occurs in **ServerScriptService** ⟩ **LaserBlastHandler** ⟩ **getValidatedBlastData** ⟩ **getValidatedRayResults**, and each check correlates to a nested module script:
|
||||
|
||||
1. First, `getValidatedRayResults` calls `validateRayResult` to check that each entry in the `rayResults` table from the client is a `Datatype.CFrame` and a `Player` (or nil).
|
||||
|
||||
1. Next, it calls `isRayAngleFromOriginValid` to compare the expected angles of the laser spread to the ones from the client. This code in particular shows the advantage of using `ReplicatedStorage` because the server can call `getDirectionsForBlast` itself, store the return as the "expected" data, and then compare it against the data from the client.
|
||||
|
||||
Just like blaster validation from the previous chapter, `isRayAngleFromOriginValid` relies on a tolerance value to determine what constitutes an "excessive" difference in angles:
|
||||
|
||||
```lua title="isRayAngleFromOriginValid"
|
||||
local claimedDirection = (rayResult.destination.Position - originCFrame.Position).Unit
|
||||
local directionErrorDegrees = getAngleBetweenDirections(claimedDirection, expectedDirection)
|
||||
|
||||
return directionErrorDegrees <= ToleranceValues.BLAST_ANGLE_SANITY_CHECK_TOLERANCE_DEGREES
|
||||
```
|
||||
|
||||
Roblox abstracts away the most involved bits of math, so the result is a short, highly reusable helper function with applicability across a range of experiences:
|
||||
|
||||
```lua title="getAngleBetweenDirections"
|
||||
local function getAngleBetweenDirections(directionA: Vector3, directionB: Vector3)
|
||||
local dotProduct = directionA:Dot(directionB)
|
||||
local cosAngle = math.clamp(dotProduct, -1, 1)
|
||||
local angle = math.acos(cosAngle)
|
||||
return math.deg(angle)
|
||||
end
|
||||
```
|
||||
|
||||
1. The next check is the most intuitive. Whereas `getValidatedBlastData` uses `DISTANCE_SANITY_CHECK_TOLERANCE_STUDS` to verify that the player who blasted was near the beam's point of origin, `isPlayerNearPosition` uses identical logic to check if the tagged player was near the beam's destination:
|
||||
|
||||
```lua title="isPlayerNearPosition"
|
||||
local distanceFromCharacterToPosition = position - character:GetPivot().Position
|
||||
if distanceFromCharacterToPosition.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
|
||||
return false
|
||||
end
|
||||
```
|
||||
|
||||
1. The final check `isRayPathObstructed` uses a variation of the ray cast operation to check if the ray's destination is behind a wall or other obstruction from the client's position. For example, if a malicious player were to systematically remove all walls from the experience to tag other players, the server would check and confirm that the rays are invalid because it knows every object position within the environment.
|
||||
|
||||
```lua title="isRayPathObstructed"
|
||||
local scaledDirection = (rayResult.destination.Position - blastData.originCFrame.Position)
|
||||
scaledDirection *= (scaledDirection.Magnitude - 1) / scaledDirection.Magnitude
|
||||
```
|
||||
|
||||
No anti-exploit strategy is comprehensive, but it's important to consider how malicious players may approach your experience so that you can put checks in place that the server can run to flag suspicious behavior.
|
||||
|
||||
## Reduce player health
|
||||
|
||||
After verifying that a player tagged another player, the final steps in completing the main gameplay loop in the sample laser tag experience are to reduce the tagged player's health, increment the leaderboard, and respawn the player back into the round.
|
||||
|
||||
Starting with reducing the tagged player's health, [Spawning and respawning](spawn-respawn.md) covers the distinction between `Class.Player` and `Class.Player.Character`, specifically that a character is a `Class.Humanoid` model. `Class.Humanoid` models have a `Class.Humanoid.Health|Health` property with a default value of 100. Rather than implementing its own system, the sample laser tag experience uses this built-in property to keep track of how much damage a player needs before they are tagged out of the round.
|
||||
|
||||
The experience stores damage values in the `damagePerHit` attribute of each blaster. For example, the blaster that blasts a single laser beam inflicts 10 points of damage, so it takes ten blasts with this blaster to tag out another player. To start the process of tagging a player out, `LaserBlastHandler` calls **ServerScriptService** ⟩ **LaserBlastHandler** ⟩ **processTaggedPlayers**, which checks the now-validated `rayResults` table for players and passes `damagePerHit` to `onPlayerTagged`.
|
||||
|
||||
<Alert severity="info">
|
||||
Note that this process occurs for each **ray**, not each player. A blast can have multiple laser beams, so a player can receive damage several times from a single blast.
|
||||
</Alert>
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Detecting-Hits/Reduce-Player-Health.png" alt="" width="80%" />
|
||||
</figure>
|
||||
|
||||
`Class.Humanoid.Health|Health` doesn't accept negative values, so `onPlayerTagged` has some logic to keep player health at or above zero. After verifying that player health is above zero, it compares health to `damagePerHit` and uses the smaller of the two values. For example, if a player has 10 health and is hit by a 15 damage laser beam, the laser only inflicts 10 points of damage.
|
||||
|
||||
This way of approaching the problem might seem a bit convoluted. For example, why not just set player health to zero if it would be negative? The reason is because setting health values circumvents the force field. Using the `Class.Humanoid:TakeDamage()` method ensures that players don't take damage while their force fields are active.
|
||||
|
||||
```lua title="onPlayerTagged"
|
||||
local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
|
||||
local character = playerTagged.Character
|
||||
local isFriendly = playerBlasted.Team == playerTagged.Team
|
||||
|
||||
-- Disallow friendly fire
|
||||
if isFriendly then
|
||||
return
|
||||
end
|
||||
|
||||
local humanoid = character and character:FindFirstChild("Humanoid")
|
||||
if humanoid and humanoid.Health > 0 then
|
||||
-- Avoid negative health
|
||||
local damage = math.min(damageAmount, humanoid.Health)
|
||||
|
||||
-- TakeDamage ensures health is not lowered if ForceField is active
|
||||
humanoid:TakeDamage(damage)
|
||||
if humanoid.Health <= 0 then
|
||||
-- Award playerBlasted a point for tagging playerTagged
|
||||
Scoring.incrementScore(playerBlasted, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The next step is to increment the leaderboard. It might have seemed unnecessary for `LaserBlastHandler` to include the player who blasted alongside the blast data, but without that information, the experience can't credit the player with tagging someone out. Finally, the tagged out player respawns back into the round, which you can review in [Spawning and Respawning](spawn-respawn.md).
|
||||
|
||||
The five chapters in this curriculum cover the experience's core gameplay loop, but there are still plenty of areas to explore, such as:
|
||||
|
||||
- **Blaster visuals**: See **ReplicatedStorage** ⟩ **FirstPersonBlasterVisuals** and **ServerScriptService** ⟩ **ThirdPersonBlasterVisuals**.
|
||||
- **Audio**: See **ReplicatedStorage** ⟩ **SoundHandler**.
|
||||
- **Custom Modes**: How could you modify this experience to introduce new types of objectives, such as scoring the most points before the time runs out?
|
||||
|
||||
For extended gameplay logic for the laser tag experience, as well as reusable, high-quality environmental assets, review the [Laser Tag](../../../resources/templates.md#laser-tag) template.
|
||||
|
||||
<Alert severity="info">
|
||||
We're interested in hearing from you about your experience following the Gameplay Scripting Curriculum. If you have any questions, concerns, or additional feedback on the process, please comment on our [Gameplay Scripting Curriculum Q&A](https://devforum.roblox.com/t/gameplay-scripting-curriculum-qa/2731896).
|
||||
</Alert>
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
---
|
||||
title: Implement blaster behavior
|
||||
description: Explains end-to-end how a blast mechanic works in a laser tag experience.
|
||||
next: /tutorials/curriculums/gameplay-scripting/detect-hits
|
||||
prev: /tutorials/curriculums/gameplay-scripting/add-rounds
|
||||
---
|
||||
|
||||
**Implementing blaster behavior** is the process of programming a blast mechanic in first-person shooter experiences. While players can blast with a single click or press of a button, creating a satisfying and accurate blast behavior is important because it enhances players' enjoyment of the overall gameplay.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you about the scripts behind implementing blaster behavior for two different types of blasters, including guidance on:
|
||||
|
||||
- Detecting when players press the blast button.
|
||||
- Checking whether the player can use their blaster if they recently pressed the blast button.
|
||||
- Generating blast data that tells the server who initiated the blast, where it came from, and what was each laser beam's final destination.
|
||||
- Notifying the server of the blast data so it can perform the appropriate actions if the blast collided with another player.
|
||||
- Resetting the blaster between each blast to give the blaster enough time to cool down before it can blast again.
|
||||
|
||||
After you complete this section, you will learn about the scripts that allow the blaster to detect when its blasts collide with other players, then deduct the corresponding amount of health according to each blaster type.
|
||||
|
||||
## Detect player input
|
||||
|
||||
The first step to implementing blaster behavior is to listen for when a player presses the blast button. The input type that players use to press the blast button depends on which device they're using to access the experience. For example, the sample laser tag experience supports mouse and keyboard controls, gamepads, and touch controls. You can see each of these input types in **ReplicatedStorage** ⟩ **UserInputHandler**.
|
||||
|
||||
This client script uses `Class.ContextActionService` to bind `MouseButton1` and `ButtonR2` to the blasting action. This means that every time a player either presses a left mouse button or a gamepad's R2 button, it triggers a laser beam to blast out of the blaster. Note that the **HUDGui** contains a button for blasting on mobile devices, which it connects to later in the script.
|
||||
|
||||
```lua title="UserInputHandler"
|
||||
ContextActionService:BindAction("_", onBlasterActivated, false,
|
||||
Enum.UserInputType.MouseButton1,
|
||||
Enum.KeyCode.ButtonR2
|
||||
)
|
||||
```
|
||||
|
||||
Another important note is the use of `Enum.UserInputState.Begin` in the `onBlasterActivated()` definition. Many user interface interactions, such as choosing a blaster in this example, don't occur until after the mouse button comes up (`Enum.UserInputState.End`), which gives users a last-second chance to avoid the interaction. However, a blasting mechanic doesn't feel responsive unless it occurs the instant the button goes down.
|
||||
|
||||
To demonstrate, you can change `Enum.UserInputState.Begin` to `Enum.UserInputState.End`, then playtest to see how the responsiveness of the blast impacts the gameplay of the experience. For example, if players can hold down the button without triggering the blast, how might that change the their experience while tagging other players?
|
||||
|
||||
```lua title="UserInputHandler"
|
||||
local function onBlasterActivated(_actionName: string,
|
||||
inputState: Enum.UserInputState, _inputObject: InputObject)
|
||||
if inputState == Enum.UserInputState.End then -- updated line, be sure to change back
|
||||
attemptBlastClient()
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Check whether the player can blast
|
||||
|
||||
After `UserInputHandler` detects a button press or screen tap, it calls **ReplicatedStorage** ⟩ **Blaster** ⟩ **attemptBlastClient** to check whether the player can blast or not. Like most checks in the sample laser tag experience, it occurs twice: first on the client, then later on the server. `attemptBlastClient` then calls **ReplicatedStorage** ⟩ **Blaster** ⟩ **canLocalPlayerBlast** to perform a simple check of the `blasterStateClient` player attribute:
|
||||
|
||||
```lua title="canLocalPlayerBlast"
|
||||
local function canLocalPlayerBlast(): boolean
|
||||
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
|
||||
end
|
||||
```
|
||||
|
||||
If you examine **ReplicatedStorage** ⟩ **Blaster** ⟩ **BlasterState**, you can see that the experience has three blaster states: `Ready`, `Blasting`, and `Disabled`. To see the effect of each of these states, you can playtest the experience, select your player under the **Players** service, then observe the **blasterStateClient** attribute in the **Properties** window. Notice how it displays `Disabled` while you choose your blaster, `Ready` most of the time, and `Blasting` for less than a second after you press the button.
|
||||
|
||||
<video controls src="../../../assets/tutorials/gameplay-scripting/Blaster-Behavior/Normal-BlastState.mp4" width="100%"></video>
|
||||
|
||||
This slight pause prevents you from being able to blast as quickly as you can click. For example, if you change the function to always return true, you can rapidly blast your blaster without any delay, which is unrealistic for laser tag gameplay.
|
||||
|
||||
```lua title="canLocalPlayerBlast"
|
||||
local function canLocalPlayerBlast(): boolean
|
||||
return true -- updated line, be sure to change back
|
||||
end
|
||||
```
|
||||
|
||||
<video controls src="../../../assets/tutorials/gameplay-scripting/Blaster-Behavior/Custom-BlastState.mp4" width="100%"></video>
|
||||
|
||||
## Generate blast data
|
||||
|
||||
After verifying that the player's blaster is in the `Ready` state, `attemptBlastClient` calls **ReplicatedStorage** ⟩ **attemptBlastClient** ⟩ **blastClient**. The first step that `blastClient` takes is to set the `blasterStateClient` player attribute to `Blasting`, which avoids the same rapid fire case from earlier.
|
||||
|
||||
<Alert severity="info">
|
||||
Changing `BlasterState` to `Blasting` also plays a sound in **ReplicatedStorage** ⟩ **SoundHandler**.
|
||||
</Alert>
|
||||
|
||||
The next step is to generate the blast data. If you review **ReplicatedStorage** ⟩ **Blaster** ⟩ **BlastData**, you can see that each blast consists of three pieces of information:
|
||||
|
||||
- The player who initiates the blast.
|
||||
- A `DataType.CFrame` that represents the blast's point of origin.
|
||||
- A `RayResult` table that contains each laser beam's final destination and the hit player, if hit another player.
|
||||
|
||||
To generate this data, `blastClient` calls **ReplicatedStorage** ⟩ **attemptBlastClient** ⟩ **blastClient** ⟩ **generateBlastData**, which you can review below.
|
||||
|
||||
```lua title="generateBlastData"
|
||||
local function generateBlastData(): BlastData.Type
|
||||
local blasterConfig = getBlasterConfig()
|
||||
|
||||
local rayDirections = getDirectionsForBlast(
|
||||
currentCamera.CFrame, blasterConfig)
|
||||
local rayResults = castLaserRay(
|
||||
localPlayer, currentCamera.CFrame.Position, rayDirections)
|
||||
|
||||
local blastData: BlastData.Type = {
|
||||
player = localPlayer,
|
||||
originCFrame = currentCamera.CFrame,
|
||||
rayResults = rayResults,
|
||||
}
|
||||
return blastData
|
||||
end
|
||||
```
|
||||
|
||||
This function starts by using `getBlasterConfig` to retrieve the player's blaster type. The sample provides two types of blasters: one that produces several beams with a wide, horizontal spread, and another that produces a single beam. You can find their configurations in **ReplicatedStorage** ⟩ **Instances** ⟩ **LaserBlastersFolder**.
|
||||
|
||||
The function then uses `currentCamera.CFrame` as the point of origin for the blast, passing it to `getDirectionsForBlast`. At this point, the code is no longer about the blaster, it's about the laser beam, which you will learn more about in the [detect hits](./detect-hits.md) section of the tutorial. Finally, after creating the `rayResults` table, `generateBlastData` has all the information it needs to return the blast data to `blastClient`.
|
||||
|
||||
## Notify the server
|
||||
|
||||
Once `blastClient` has complete data for the blast, it fires two events:
|
||||
|
||||
```lua title="blastClient"
|
||||
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
|
||||
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
|
||||
|
||||
laserBlastedBindableEvent:Fire(blastData)
|
||||
laserBlastedEvent:FireServer(blastData)
|
||||
```
|
||||
|
||||
The `Class.BindableEvent` notifies other client scripts of the blast. For example, **ReplicatedStorage** ⟩ **FirstPersonBlasterVisuals** uses this event to know when to display visual effects, such as the blast animation and cooldown bar. Similarly, the `Class.RemoteEvent` notifies server scripts of the blast, which begins processing the blast in **ServerScriptService** ⟩ **LaserBlastHandler**.
|
||||
|
||||
```lua title="LaserBlastHandler"
|
||||
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
|
||||
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
|
||||
if not validatedBlastData then
|
||||
return
|
||||
end
|
||||
|
||||
if not canPlayerBlast(playerBlasted) then
|
||||
return
|
||||
end
|
||||
|
||||
blastServer(playerBlasted)
|
||||
|
||||
processTaggedPlayers(playerBlasted, blastData)
|
||||
|
||||
for _, replicateToPlayer in Players:GetPlayers() do
|
||||
if playerBlasted == replicateToPlayer then
|
||||
continue
|
||||
end
|
||||
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
To help prevent cheating, the server must verify all data that each client sends. These checks include:
|
||||
|
||||
1. Is `BlastData` a table? Does it contain a `Class.CFrame` and another table named `rayResults`?
|
||||
1. Does the player have a blaster equipped?
|
||||
1. Does the player have a character and a location within the world?
|
||||
1. After sending the blast data, has the player moved an excessive distance away from where they blasted the laser beam?
|
||||
|
||||
This last check involves a judgment call, and according to server latency and player movement speed, you might decide that different values are excessive for your own experience. To demonstrate how to make this judgment call, you can get a sense of the typical magnitude of positional change by adding a print statement in `getValidatedBlastData` and playtesting the experience.
|
||||
|
||||
```lua title="getValidatedBlastData"
|
||||
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
|
||||
print(distanceFromCharacterToOrigin.Magnitude) -- updated line, be sure to remove
|
||||
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
|
||||
warn(`Player {player.Name} failed an origin sanity check while blasting`)
|
||||
return
|
||||
end
|
||||
```
|
||||
|
||||
As you move and blast, note the output. It might look something like this:
|
||||
|
||||
```text
|
||||
1.9019629955291748
|
||||
3.1549558639526367
|
||||
2.5742883682250977
|
||||
4.8044586181640625
|
||||
2.6434271335601807
|
||||
```
|
||||
|
||||
If you increase the movement speed for players in **ReplicatedStorage** ⟩ **PlayerStateHandler** ⟩ **togglePlayerMovement**, then playtest again, you will likely encounter many failed checks due to excessive movement between blasts.
|
||||
|
||||
```lua title="togglePlayerMovement"
|
||||
local ENABLED_WALK_SPEED = 60 -- updated line, be sure to change back
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
For this reason, if you decide to increase movement speed, consider adjusting `DISTANCE_SANITY_CHECK_TOLERANCE_STUDS` in **ServerStorage** ⟩ **ToleranceValues**. For more information on how to approach this problem, see [Movement Validation](../../../scripting/security/security-tactics.md#movement-validation).
|
||||
</Alert>
|
||||
|
||||
The server then does the following:
|
||||
|
||||
- Validates `rayResults`.
|
||||
- Checks whether the player can blast.
|
||||
- Resets the blaster state.
|
||||
- Reduces health for any tagged players.
|
||||
- Replicates the blast to all other players so that they can see third-person visuals.
|
||||
|
||||
For more information on these server operations, see the [detect hits](./detect-hits.md) section of the tutorial.
|
||||
|
||||
## Reset the blaster
|
||||
|
||||
In the sample laser tag experience, blasters use a heat mechanic. Rather than reloading after a set number of blasts, they need time to "cool down" between each blast. This same cooldown delay occurs on both the client (`blastClient`) and the server (`blastServer`), with the server acting as the source of truth.
|
||||
|
||||
```lua title="blastServer"
|
||||
local blasterConfig = getBlasterConfig(player)
|
||||
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
|
||||
|
||||
task.delay(secondsBetweenBlasts, function()
|
||||
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
|
||||
if currentState == BlasterState.Blasting then
|
||||
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
The `secondsBetweenBlasts` attribute is part of the blaster configuration in **ReplicatedStorage** ⟩ **Instances** ⟩ **LaserBlastersFolder**. After the `secondsBetweenBlasts` delay passes, the player can blast again, and the entire process repeats. To help the player understand when they can blast again, the experience includes a cooldown bar.
|
||||
|
||||
At this point, players can spawn and respawn, aim and blast, but the experience still has to determine the results of each blast. In the next section of the tutorial, you will learn how to program the ability for the blaster to detect when the blast hits another player, then reduce the appropriate amount of player health according to blaster settings.
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: Gameplay scripting curriculum
|
||||
description: Learn how to implement and organize a complex scripting project.
|
||||
next: /tutorials/curriculums/gameplay-scripting/create-teams
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid alignItems="stretch" container direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<div class="container" style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/7iJKUUiKc0Y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "95%", height: "95%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
|
||||
**Gameplay scripting** is the discipline of programming the behavior that makes experiences
|
||||
engaging and fun to play, such as their logic, movement, events, and interactions between
|
||||
objects in the 3D space.
|
||||
|
||||
You'll learn the gameplay scripting for a first-person shooter laser tag experience by
|
||||
following the general organization and key implementation details of a large, complex
|
||||
project, including several opportunities to create new behavior with custom values.
|
||||
|
||||
This course is intended for readers who are familiar with general coding concepts, Studio's
|
||||
data model, and the client-server relationship. If you need help learning how to code, try [coding fundamentals](../../fundamentals/coding-1/coding-fundamentals.md).
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
## Course Contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Create teams</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
Learn how to [sort players into teams](create-teams.md) when a round is active.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Spawn and respawn</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
Learn how to [trigger unique actions](spawn-respawn.md) when players spawn and respawn back into a round.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Add rounds</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
Learn how to [implement rounds](add-rounds.md) that track points, display final results, and reset teams.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Implement blasters</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
Learn how to [script blast behavior](implement-blasters.md) for two different blaster types.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 5 - Detect hits</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
Learn how to [perform hit detection](detect-hits.md) from blast data, and allocate damage to reduce player health.
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,381 @@
|
|||
---
|
||||
title: Spawn and respawn
|
||||
description: Explains how spawning and respawning works in a laser tag experience.
|
||||
next: /tutorials/curriculums/gameplay-scripting/add-rounds
|
||||
prev: /tutorials/curriculums/gameplay-scripting/create-teams
|
||||
---
|
||||
|
||||
**Spawning** is the process of creating an object or character in an experience, and **respawning** is the process of adding an object or character back into an experience after they meet a removal condition, such as a character's health reaching zero or falling off the map. Both processes are important because they ensure players are able to join your experience, and can continue playing to improve their skills.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) as a reference, this section of the tutorial teaches you how to use and customize Roblox's built-in features to handle spawning and respawning, including scripting guidance on:
|
||||
|
||||
- Configuring spawn locations so that players can only spawn into their team's spawn zone.
|
||||
- Adding new players and their character to the round as they join the experience.
|
||||
- Customizing force fields that prevent damage as players spawn and respawn.
|
||||
- Handling client state so gameplay works correctly at the appropriate time.
|
||||
- Respawning characters after they are tagged out of the round.
|
||||
- Performing small, miscellaneous actions that are crucial for setting gameplay and character parameters.
|
||||
|
||||
This section includes plenty of scripting content, but instead of writing everything from scratch when creating an experience, it encourages you to leverage existing components, rapidly iterate, and figure out which systems need a custom implementation to match your vision. After you complete this section, you will learn how to implement round-based gameplay that tracks points, monitors player state, and displays round results.
|
||||
|
||||
## Configure spawn locations
|
||||
|
||||
If you were to playtest the experience right now, all players would randomly spawn at either the `Class.SpawnLocation` object in the green team's spawn zone, or the `Class.SpawnLocation` object in the pink team's spawn zone. This presents a gameplay issue where players could tag each other within each spawn zone as soon as their opponent's force field disappears.
|
||||
|
||||
To combat this problem, the sample laser tag experience configures both spawn locations with a `Class.SpawnLocation.Neutral|Neutral` property set to **false** to restrict the opposing team's players from spawning in the wrong spawn zone, and a `Class.SpawnLocation.TeamColor|TeamColor` property set to the corresponding `Class.Team.Color` value from [Assign Team Colors](create-teams.md#assign-team-colors) in the previous section of the tutorial:
|
||||
|
||||
- **TeamASpawn** – The spawn location in the green team's spawn zone with a `Class.SpawnLocation.TeamColor|TeamColor` property set to **Mint**.
|
||||
- **TeamBSpawn** – The spawn location in the pink team's spawn zone with a `Class.SpawnLocation.TeamColor|TeamColor` property set to **Carnation Pink**.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/TeamASpawn.jpg" width="100%"/>
|
||||
<figcaption>TeamASpawn</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/TeamBSpawn.jpg" width="100%"/>
|
||||
<figcaption>TeamBSpawn</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="warning">
|
||||
The `Class.SpawnLocation.TeamColor` property is different from the `Class.SpawnLocation.BrickColor` and `Class.SpawnLocation.Color` properties which represent the visual color of the spawn object and are not related to team functionality.
|
||||
</Alert>
|
||||
|
||||
When a player joins the experience, **ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** ⟩ **spawnPlayersInMap** checks to see how many players are already on each team, then returns the team with the least amount of players.
|
||||
|
||||
```lua title="spawnPlayersInMap"
|
||||
local function getSmallestTeam(): Team
|
||||
local teams = Teams:GetTeams()
|
||||
|
||||
-- Sort teams in ascending order from smallest to largest
|
||||
table.sort(teams, function(teamA: Team, teamB: Team)
|
||||
return #teamA:GetPlayers() < #teamB:GetPlayers()
|
||||
end)
|
||||
|
||||
-- Return the smallest team
|
||||
return teams[1]
|
||||
end
|
||||
```
|
||||
|
||||
Once it knows the team with the least amount of players, it sorts the player into that team, sets their `Class.Player.Neutral` property to **false** so the player can only spawn and respawn to their team's spawn location, then sets their `PlayerState` to `SelectingBlaster`, which you'll learn more about later in the tutorial.
|
||||
|
||||
```lua title="spawnPlayersInMap"
|
||||
local function spawnPlayersInMap(players: { Player })
|
||||
for _, player in players do
|
||||
player.Team = getSmallestTeam()
|
||||
player.Neutral = false
|
||||
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
|
||||
task.spawn(function()
|
||||
player:LoadCharacter()
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If you examine **Workspace** ⟩ **World** ⟩ **Map** ⟩ **Spawns**, you can see that there is one more spawn location in the map: **NeutralSpawn**. This spawn location is unique from the others because it doesn't have a `Class.SpawnLocation.TeamColor|TeamColor` property set to one of the two teams in the experience; instead, this spawn location has a `Class.SpawnLocation.Neutral|Neutral` property that changes depending on if a round is active.
|
||||
|
||||
For example, if the round is active, the `Class.SpawnLocation.Neutral|Neutral` property sets to **false** so **spawnPlayersInMap** can sort players into teams and spawn them into the arena. However, if the round isn't active, such as the time between one round and the next, the `Class.SpawnLocation.Neutral|Neutral` property sets to **true** so players can spawn there regardless of their team status. This process is what makes the **Neutral** spawn location a functional lobby.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/Neutral.jpg" alt="" width="100%" />
|
||||
<figcaption>Neutral</figcaption>
|
||||
</figure>
|
||||
|
||||
To demonstrate, if you examine **ServerScriptService** ⟩ **Gameplay** ⟩ **Rounds** ⟩ **SpawnPlayersInLobby**, which runs at the end of a round, you can see that for every player that is passed into the `players: { Player }` table, the script:
|
||||
|
||||
- Sets their `Class.Player.Neutral` property to true to automatically reset their `Class.Player.Team` to `nil`, allowing the player to respawn in the lobby when a round isn't active, as the spawn location's `Class.SpawnLocation.Neutral|Neutral` property is also set to **true**.
|
||||
- Changes their `PlayerState` to `InLobby` to remove the player's blaster and first-person UI visuals.
|
||||
|
||||
For more information on the neutral spawn zone and its functionality for each round, see [Adding Rounds](adding-rounds.md) in the next section of the tutorial.
|
||||
|
||||
```lua title="spawnPlayersInLobby"
|
||||
local function spawnPlayersInLobby(players: { Player })
|
||||
for _, player in players do
|
||||
player.Neutral = true
|
||||
player:SetAttribute(PlayerAttribute.playerState, PlayerState.InLobby)
|
||||
task.spawn(function()
|
||||
player:LoadCharacter()
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Connect new players
|
||||
|
||||
Luau code in Studio is often event-driven, meaning that scripts listen for events from a Roblox service, then call a function in response. For example, when adding new players to a multiplayer experience, there must be an event that handles everything necessary for players to connect successfully. In the sample laser tag experience, this corresponding event is `Players.PlayerAdded:Connect`.
|
||||
|
||||
`Players.PlayerAdded:Connect` is a part of multiple scripts in the experience. If you use the <kbd>Ctrl/Cmd+Shift+F</kbd> shortcut and search for `Players.PlayerAdded:Connect`, the results provide a good starting point for understanding the experience's initial setup.
|
||||
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/Find-All.png" alt="Studio's Find All window with the Players.PlayerAdded results highlighted." width="80%"/>
|
||||
|
||||
To demonstrate, open **ServerScriptService** ⟩ **SetupHumanoid**. The distinction between `Class.Player` and `Class.Player.Character|Character` is key to understanding this script:
|
||||
|
||||
- A **player** is a connected client, and a **character** is a `Class.Humanoid` model.
|
||||
- Players need to choose a blaster and be added to the leaderboard. Characters need to spawn and receive a blaster.
|
||||
|
||||
`SetupHumanoid` immediately checks if the player has a character (just joined) or doesn't (is respawning). After it finds one, it calls `onCharacterAdded()`, gets the `Class.Humanoid` model from the character, and passes it to **ServerScriptService** ⟩ **SetupHumanoid** ⟩ **setupHumanoidAsync** for customization. After setting these values, the script then waits for the character's health to reach zero. You will learn more about respawning later in this section of the tutorial.
|
||||
|
||||
```lua title="setupHumanoidAsync"
|
||||
local function setupHumanoidAsync(player: Player, humanoid: Humanoid)
|
||||
|
||||
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.Subject
|
||||
humanoid.NameDisplayDistance = 1000
|
||||
humanoid.HealthDisplayDistance = 1000
|
||||
humanoid.NameOcclusion = Enum.NameOcclusion.OccludeAll
|
||||
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOn
|
||||
humanoid.BreakJointsOnDeath = false
|
||||
|
||||
humanoid.Died:Wait()
|
||||
onHumanoidDied(player, humanoid)
|
||||
end
|
||||
```
|
||||
|
||||
The important note with this script is that the properties are completely optional, meaning that if you remove the first six lines of the function, the experience still works properly. Rather than being functional requirements, each property allows you to make design decisions that meet your gameplay goals. For example:
|
||||
|
||||
- If you want character names to display at closer distances, reduce the value of `Class.Humanoid.NameDisplayDistance`.
|
||||
- If you only want a character's health to display if it's below 100%, set `Class.Humanoid.HealthDisplayType` to **DisplayWhenDamaged**.
|
||||
- If you want characters to break apart when their health reaches 0, set `Class.Humanoid.BreakJointsOnDeath` to **True**.
|
||||
|
||||
If you change the values of these properties, it's important to playtest so that you can see the impact of your new settings. You can recreate what players experience in a [multi‑client simulation](../../../studio/testing-modes.md#multi-client-simulation) by selecting at least two characters in a **Server & Clients** playtest from the mezzanine.
|
||||
|
||||
<img src="../../../assets/studio/general/Mezzanine-Testing-Mode-Server-Clients.png" width="800" alt="Server & Clients option in the testing modes dropdown of Studio's mezzanine." />
|
||||
|
||||
<Alert severity="info">
|
||||
Try changing some of these values and playtesting with multiple players to see how they affect the gameplay of the experience. For instance, how might player strategy change if you chose to hide the health display?
|
||||
</Alert>
|
||||
|
||||
Another example of the `Players.PlayerAdded:Connect` event is in **ServerScriptService** ⟩ **PlayerStateHandler**. Just like in the previous example, `PlayerStateHandler` immediately checks for a character. If the player isn't in the lobby, the script sets a player attribute to the `SelectingBlaster` state, the initial state for a round in which players can select from one of two different blaster types after spawning into the arena. This state also includes a force field that prevents players from taking damage while they're making their selection.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onPlayerAdded(player: Player)
|
||||
player.CharacterAdded:Connect(function()
|
||||
if not player.Neutral then
|
||||
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
|
||||
onPlayerStateChanged(player, PlayerState.SelectingBlaster)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
One particular variable in `PlayerStateHandler` warrants discussion: `attributeChangedConnectionByPlayer`. This table stores all players and their `Datatype.RBXScriptConnection|Connections` to the `GetAttributeChangedSignal`. The reason for storing this connection in a table is so that `PlayerStateHandler` can **disconnect** it when the player leaves the experience. This process serves as a sort of memory management to prevent the number of connections from growing ever-larger over time.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local attributeChangedConnectionByPlayer = {}
|
||||
local function onPlayerAdded(player: Player)
|
||||
|
||||
-- Handle all future updates to player state
|
||||
attributeChangedConnectionByPlayer[player] = player
|
||||
:GetAttributeChangedSignal(PlayerAttribute.playerState)
|
||||
:Connect(function()
|
||||
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
|
||||
onPlayerStateChanged(player, newPlayerState)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Disconnect from the attribute changed connection when the player leaves
|
||||
local function onPlayerRemoving(player: Player)
|
||||
if attributeChangedConnectionByPlayer[player] then
|
||||
attributeChangedConnectionByPlayer[player]:Disconnect()
|
||||
attributeChangedConnectionByPlayer[player] = nil
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You can see that both connected functions in `onPlayerAdded()` call `onPlayerStateChanged()`. During initial setup after a player sorts into a team, `onPlayerAdded()` sets `PlayerState` to `SelectingBlaster`, so the first `if` statement evaluates to false and disables the `BlasterState`. In the later [Implement blasters](./implement-blasters.md) section of the tutorial, you will learn more details about this process.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onPlayerStateChanged(player: Player, newPlayerState: string)
|
||||
-- Blaster state is 'Ready' only if player state is 'Playing'
|
||||
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
|
||||
|
||||
-- Schedule the destroy force field logic when the player begins playing
|
||||
if newPlayerState == PlayerState.Playing then
|
||||
scheduleDestroyForceField(player)
|
||||
end
|
||||
|
||||
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
|
||||
end
|
||||
```
|
||||
|
||||
If you add breakpoints or even just a `print()` statement, you can see that `onPlayerStateChanged()` gets called frequently throughout the experience: such as during initial setup of a round, to set itself on the main code path, after the player chooses a blaster, and when the player returns to the lobby, or the **Neutral** spawn location. Furthermore, after the player chooses a blaster, **ServerScriptService** ⟩ **BlasterSelectedHandler** sets the `PlayerState` to `Playing`, and `PlayerStateHandler` can finally remove the force field by calling `scheduleDestroyForceField()`.
|
||||
|
||||
## Customize force fields
|
||||
|
||||
Instead of using a custom implementation, the sample laser tag experience uses Studio's built-in `Class.ForceField` class to prevent players from taking damage while they're selecting their blaster. This ensures that the only requirement for players to spawn with a force field is to include spawn locations with a `Class.SpawnLocation.Duration` property that is greater than 0. The sample uses an arbitrary value of 9,999 to enable force fields, then handles the actual duration programmatically in **ReplicatedStorage** ⟩ **ForceFieldClientVisuals**.
|
||||
|
||||
Similar to `setupHumanoidAsync`, most of the lines in `ForceFieldClientVisuals` are optional. For example, if you comment out the contents of the function like the following script does, the experience uses the default sparkling force field instead of the hexagonal script in **StarterGui** ⟩ **ForceFieldGui**.
|
||||
|
||||
```lua title="Commenting Out Properties in ForceFieldClientVisuals"
|
||||
local function onCharacterAddedAsync(character: Model)
|
||||
-- local forceField = character:WaitForChild("ForceField", 3)
|
||||
-- if not forceField then
|
||||
-- return
|
||||
-- end
|
||||
-- forceField.Visible = false
|
||||
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = true
|
||||
-- forceField.Destroying:Wait()
|
||||
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = false
|
||||
end
|
||||
```
|
||||
|
||||
Because the custom force field is a GUI rather than a new `Class.ParticleEmitter`, the `ForceFieldClientVisuals` script only affects the first-person visuals for each player, **not** third-person visuals when players look at other players. Third-person visuals retain the default Roblox appearance. For more information on modifying force fields, see `Class.ForceField.Visible`.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/First-Person-Visuals.png" alt="First-person force field visuals include a futuristic hexagonal grid on the perimeter of the screen." width="100%"/>
|
||||
<figcaption>First-person force field visuals</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/Third-Person-Visuals.png" alt="Third-person force field visuals include a blue sparkling orb around the player spawning into the experience." width="100%"/>
|
||||
<figcaption>Third-person force field visuals</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Force fields are useful because they provide players enough time to between spawning and respawning without needing to worry about enemy players, but eventually they need to disappear for the main laser tag gameplay. The script that handles force field removal is in **ReplicatedStorage** ⟩ **scheduleDestroyForceField**, and it checks for three unique conditions:
|
||||
|
||||
- After players select a blaster, force fields need to last long enough to allow players to acclimate to their surroundings.
|
||||
- During this acclimation time, force fields can't be an advantage, so they need to disappear the moment a player blasts their blaster.
|
||||
- Force fields need to disappear when players reset their characters either before blasting or before the force field times out.
|
||||
|
||||
Each of these checks in the `scheduleDestroyForceField` script call `endForceField()` for these conditions.
|
||||
|
||||
```lua title="scheduleDestroyForceField"
|
||||
-- End force field if player blasts
|
||||
local blasterStateAttribute = getBlasterStateAttribute()
|
||||
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
|
||||
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
|
||||
if currentBlasterState == BlasterState.Blasting then
|
||||
endForceField()
|
||||
end
|
||||
end)
|
||||
|
||||
-- End force field if player resets
|
||||
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
|
||||
|
||||
-- End force field after 8 seconds
|
||||
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
|
||||
```
|
||||
|
||||
`endForceField()` includes a seemingly odd `if` statement around the `forceFieldEnded` boolean. Because the checks run sequentially, the script can call the `endForceField()` function two or even three times. The `forceFieldEnded` boolean ensures that the function only tries to destroy a force field once.
|
||||
|
||||
```lua title="scheduleDestroyForceField"
|
||||
local function endForceField()
|
||||
if forceFieldEnded then
|
||||
return
|
||||
end
|
||||
forceFieldEnded = true
|
||||
|
||||
attributeChangedConnection:Disconnect()
|
||||
characterRespawnedConnection:Disconnect()
|
||||
destroyForceField(player)
|
||||
end
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
To learn more about dealing with this type of situation, see [Debounce Patterns](../../../scripting/debounce.md).
|
||||
</Alert>
|
||||
|
||||
## Handle client state
|
||||
|
||||
While most of this section focuses on **ServerScriptService** ⟩ **PlayerStateHandler**, there's another script of the same name in **ReplicatedStorage**. The reason for the split is the client-server architecture:
|
||||
|
||||
- The client needs to understand player state information so that it can respond appropriately in real time, such as displaying the right user interface elements, or enabling players to move and blast.
|
||||
|
||||
- The server needs all this same information so that it can prevent exploits. For example, the server also needs player state to perform actions like spawning and equipping characters, disabling force fields, and displaying a leaderboard. This is why this script is in **ReplicatedStorage** and not a purely client-side location.
|
||||
|
||||
To see this core logic, review the following script in **ReplicatedStorage** ⟩ **PlayerStateHandler** that verifies the user's current state, then calls the appropriate function that handles the corresponding actions for that state.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onPlayerStateChanged(newPlayerState: string)
|
||||
if newPlayerState == PlayerState.SelectingBlaster then
|
||||
onSelectingBlaster()
|
||||
elseif newPlayerState == PlayerState.Playing then
|
||||
onPlaying()
|
||||
elseif newPlayerState == PlayerState.TaggedOut then
|
||||
onTaggedOut()
|
||||
elseif newPlayerState == PlayerState.InLobby then
|
||||
onInLobby()
|
||||
else
|
||||
warn(`Invalid player state ({newPlayerState})`)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
All event responses are logically grouped together in this script because they require similar behavior of enabling or disabling player controls, camera movement, and which UI layer is visible. For example, during blaster selection, players need to be both invulnerable and unable to move. The server already handles the force field, but the client handles movement. To demonstrate, if you check the logic for the `onSelectingBlaster()` function, you can see that the client disables player movement while they're selecting a blaster.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onSelectingBlaster()
|
||||
togglePlayerCamera(true)
|
||||
togglePlayerMovement(false)
|
||||
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
|
||||
|
||||
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
|
||||
end
|
||||
```
|
||||
|
||||
The `onPlaying()` function is similarly straightforward. It enables movement, transitions to the main heads-up display (HUD), enables the blaster, and calls the same force field function as the server.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onPlaying()
|
||||
togglePlayerMovement(true)
|
||||
setGuiExclusivelyEnabled(playerGui.HUDGui)
|
||||
|
||||
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
|
||||
|
||||
scheduleDestroyForceField()
|
||||
end
|
||||
```
|
||||
|
||||
## Respawn characters
|
||||
|
||||
The sample laser tag experience handles respawning character back into a round through the `onTaggedOut()` state in **ReplicatedStorage** ⟩ **PlayerStateHandler**. Like the `onSelectingBlaster()` and `onPlaying()` state, `onTaggedOut()` triggers unique behavior according to the changes to the `playerState` attribute. Specifically, it disables player movement, presents the respawn UI, and disables the blaster.
|
||||
|
||||
```lua title="PlayerStateHandler"
|
||||
local function onTaggedOut()
|
||||
-- Disable controls while tagged out
|
||||
togglePlayerMovement(false)
|
||||
togglePlayerCamera(false)
|
||||
setGuiExclusivelyEnabled(playerGui.OutStateGui)
|
||||
|
||||
-- Disable blaster while tagged out
|
||||
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
|
||||
end
|
||||
```
|
||||
|
||||
If you want to test this behavior, you can press <kbd>Esc</kbd>, navigate to the **Settings** tab, then click the **Reset Character** button. Notice that when you trigger the respawn screen, you cannot move, rotate the camera, or blast your blaster.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/Reset-Character-Button.png" alt="Roblox's settings menu with the Reset Character button highlighted." width="80%"/>
|
||||
<figcaption>Reset Character Button</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Spawn-Respawn/Respawn-Screen.png" alt="The respawn screen displays as a player respawns back into the round." width="100%"/>
|
||||
<figcaption>Respawn Screen</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
It's important to note that this script doesn't actually respawn characters, it just stops them from acting and provides visual feedback to players that the **server** is respawning their characters. To demonstrate, if you examine **ServerScriptService** ⟩ **SetupHumanoid** ⟩ **setupHumanoidAsync** ⟩ **onHumanoidDied**, the script sets `PlayerState` to `TaggedOut` (essentially notifying **ReplicatedStorage** ⟩ **PlayerStateHandler**), and adds some visual indicators. The actual logic of respawning is a built-in Roblox behavior.
|
||||
|
||||
When players respawn back into the round, they respawn at their team's spawn location according to the `Class.SpawnLocation.TeamColor` property. To customize the respawn time, you can add the following line to the top of `SetupHumanoid`. To learn more about this technique, see `Class.Players.RespawnTime`.
|
||||
|
||||
```lua title="SetupHumanoid"
|
||||
local Players = game:GetService("Players")
|
||||
Players.RespawnTime = 10 -- new line, in seconds
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
In an experience with a progression system, such as checkpoints, you can use `Class.Player.RespawnLocation` to specify an exact location for players to respawn to after their health reaches zero.
|
||||
</Alert>
|
||||
|
||||
## Miscellaneous setup
|
||||
|
||||
As part of initial setup, the sample laser tag experience also performs some small, but critical steps:
|
||||
|
||||
- The experience includes an empty script named **StarterPlayer** ⟩ **StarterCharacterScripts** ⟩ **Health** that disables the default Roblox health regeneration. For an explanation of this property's behavior, see `Class.Humanoid.Health`.
|
||||
|
||||
- The experience uses a first-person camera by setting the `StarterPlayer.CameraMode.LockFirstPerson` property. Note that if you want to let users change between first- and third-person cameras, you must change the property programmatically rather than just setting it once in Studio, and modify the controls and UI to compensate for the change in perspective.
|
||||
|
||||
- The experience uses the built-in Roblox leaderboard with the unit of "points", which players earn each time they tag another player out. You can see the configuration in **ServerScriptService** ⟩ **SetupLeaderboard**, but [In-Experience Leaderboards](../../../players/leaderboards.md) offers a full overview. Note that `onPlayerTagged` adds points to the leaderboard, which you'll learn about in [Add rounds](./add-rounds.md) and [Detect hits](detect-hits.md).
|
||||
|
||||
Now that players can spawn, choose a blaster, and aim it from a first-person point of view, the next section teaches you about the scripts behind creating round-based gameplay.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
title: Create Roblox account
|
||||
description: Explains how to create a Roblox account.
|
||||
next: /tutorials/curriculums/studio/install-studio
|
||||
prev: /tutorials/curriculums/studio/
|
||||
---
|
||||
|
||||
Welcome to creating on Roblox! Whether you came to this tutorial as a player, a developer from another engine, or as someone who is brand new to development, the platform has a spot for you to create anything that you can imagine.
|
||||
|
||||
Before you start learning about the Roblox engine, it's important to first create a Roblox account. This process is free and it allows you to access the Roblox app to play experiences, as well as Roblox Studio to build experiences.
|
||||
|
||||
To create a Roblox account:
|
||||
|
||||
1. Go to Roblox's [home page](https://www.roblox.com/CreateAccount). Because you don't have an account yet, the sign up screen displays.
|
||||
2. Follow each prompt, then click the **Sign Up** button. After a moment, Roblox logs you in and the home page displays.
|
||||
|
||||
<img src="../../../assets/education/legacy/module2-homepage.jpg" width="70%" />
|
||||
|
||||
With your new account, remember these safety tips:
|
||||
|
||||
- **Never share your password** — This tip applies to even real life friends.
|
||||
- **Make your password hard to guess** — If your username is "bloxcool", your password should not be "bloxcool123".
|
||||
- **Roblox employees will never ask for your password** — Report anyone who asks using the [Report Abuse](https://en.help.roblox.com/hc/articles/203312410) feature.
|
||||
- **There's no such thing as free Robux** — Never trust players or sites who say they have a way to get free Robux!
|
||||
|
||||
<Alert severity="info">
|
||||
For more account security information, see [Keep Your Account Safe](https://en.help.roblox.com/hc/en-us/articles/203313380-Keep-Your-Account-Safe).
|
||||
</Alert>
|
||||
|
||||
Now that you have an official Roblox account, it's time to install Roblox Studio!
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
title: Explore Studio UI
|
||||
description: Explains how to navigate Studio's fundamental UI.
|
||||
next: /tutorials/curriculums/studio/next-steps
|
||||
prev: /tutorials/curriculums/studio/install-studio
|
||||
---
|
||||
|
||||
import StudioCameraControls from '../../../includes/studio/camera-controls.md'
|
||||
|
||||
Welcome to your first session in Roblox Studio! Before you jump in and begin building experiences, let's take a moment to walk through Studio's essential UI. While this overview provides a solid foundation to get started, there are many additional windows and tools that you can discover and customize in the [Studio interface](../../../studio/ui-overview.md).
|
||||
|
||||
<Alert severity="info">
|
||||
<AlertTitle style={{marginBottom: 8}}>Coming to Studio from another game engine?</AlertTitle>
|
||||
Check out one of the following guides that explains the key differences between Studio and Unity or Unreal: [Roblox for Unity developers](../../../unity.md) or [Roblox for Unreal developers](../../../unreal.md).
|
||||
</Alert>
|
||||
|
||||
## Mezzanine
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/Mezzanine.jpg" width="960" alt="Mezzanine indicated at the top of Studio's window." />
|
||||
|
||||
The **mezzanine** is the top-most section of Studio and it includes:
|
||||
|
||||
- [Playtest options](../../../studio/testing-modes.md) that allow you to simulate what players see on their devices.
|
||||
- Tabs like **Model**, **Avatar**, and **Script** that filter the [toolbar](#toolbar) tools for their respective tasks.
|
||||
- [Collaborator](../../../projects/collaboration.md) information for experiences that you work on with others.
|
||||
- The [Assistant](../../../assistant/guide.md) tool that you can use as an aid to supplement your skills and assist with development tasks.
|
||||
|
||||
## Toolbar
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/Toolbar.jpg" width="960" alt="Toolbar indicated below the mezzanine bar near the top of Studio's window." />
|
||||
|
||||
The **toolbar** is the section right below the mezzanine that displays all tools and insertable objects that relate to your active tab:
|
||||
|
||||
- **Home** contains the core [transform](../../../parts/index.md#transform-parts) tools, [part inserter](../../../parts/index.md#insert-parts), the [color](../../../parts/index.md#color) and [material](../../../parts/index.md#material) widgets, the [group](../../../parts/index.md#group) and [lock](../../../parts/index.md#lock) tools, and [anchor](../../../parts/index.md#anchor) toggle. Also contains the [Terrain Editor](../../../studio/terrain-editor.md).
|
||||
|
||||
- **Model** contains the core [transform](../../../parts/index.md#transform-parts) tools, the [pivot](../../../studio/pivot-tools.md) and [align](../../../studio/align-tool.md) tools, insertion widgets for [effects](../../../effects/index.md) and [constraints](../../../physics/index.md#constraints), and [solid‑modeling](../../../parts/solid-modeling.md) tools.
|
||||
|
||||
- **Avatar** contains the core [transform](../../../parts/index.md#transform-parts) tools, as well as specialized tools for building default rigs, configuring [avatars](../../../avatar-setup/index.md), working with [animations](../../../animation/index.md), and [creating/fitting accessories](../../../art/accessories/accessory-fitting-tool.md).
|
||||
|
||||
- **UI** contains insertion widgets for [UI objects](../../../ui/index.md#ui-objects) and lets you access the [Style Editor](../../../ui/styling/editor.md), a comprehensive tool that allows you to create, manage, and apply UI styles.
|
||||
|
||||
- **Script** contains tools for writing and testing scripts, including [debugging](../../../studio/debugging.md) tools.
|
||||
|
||||
- **Plugins** contains [plugins](../../../studio/plugins.md) created by the community or plugins you've created yourself to use across your experiences.
|
||||
|
||||
When you are more comfortable with Studio, you can also create [custom tabs](../../../studio/ui-overview.md#custom-tabs) for your own development needs.
|
||||
|
||||
## 3D viewport
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/3D-Viewport.jpg" width="960" alt="3D viewport indicated in center of Studio's window." />
|
||||
|
||||
The **3D viewport** is the largest window in Studio that provides you a view into the 3D space. After you click in the 3D viewport, you can move the camera, edit objects with your mouse, and playtest the experience from your players' point-of-view.
|
||||
|
||||
For common camera controls, see the following table.
|
||||
|
||||
<StudioCameraControls components={props.components} />
|
||||
|
||||
<Alert severity="info">
|
||||
It's **strongly** recommended to use a 2-button mouse with a scroll wheel to make navigating the 3D space easier.
|
||||
</Alert>
|
||||
|
||||
## Toolbox
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/Toolbox.jpg" width="960" alt="Toolbox shown on the left side of Studio's window." />
|
||||
|
||||
The [Toolbox](../../../projects/assets/toolbox.md) contains a selection of models, images, meshes, audio, plugins, videos, and fonts from either Roblox or fellow creators on the platform. It offers four tabs:
|
||||
|
||||
- **Creator Store** is a marketplace of both free and for sale assets to use within your experiences.
|
||||
- **Inventory** contains assets that you or your groups have uploaded, or those that you found on the Creator Store.
|
||||
- **Recent** contains your most recently used assets.
|
||||
- **Creations** is similar to **Inventory**, but it excludes assets from the Creator Store.
|
||||
|
||||
## Explorer
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/Explorer.jpg" width="960" alt="Explorer window shown on the right side of Studio's window." />
|
||||
|
||||
The [Explorer](../../../studio/explorer.md) window displays a hierarchical list of every object and service that runs out-of-the-box gameplay logic for the active **place** of your experience. You can think of places like levels inside of a game.
|
||||
|
||||
<Alert severity="info">
|
||||
Places are comparable to scenes in Unity or maps in Unreal Engine.
|
||||
</Alert>
|
||||
|
||||
Experiences can have one or multiple places that each contain all components for that portion of the experience, including its specific environment, 3D objects, and scripts; these components collectively are called the **data model**. When you switch to a different place, the **Explorer** window updates accordingly to show that place's data model.
|
||||
|
||||
For more information on how the **Explorer** window organizes objects in the data model, see [Data model](../../../projects/data-model.md).
|
||||
|
||||
## Properties
|
||||
|
||||
<img src="../../../assets/tutorials/studio-lesson/Properties.jpg" width="960" alt="Properties window shown on the right side of Studio's window." />
|
||||
|
||||
The [Properties](../../../studio/properties.md) window displays a selection of properties for a selected object that you can customize to change how the object looks and behaves in the 3D environment. Every time you select a different object in the 3D viewport or **Explorer** window, the **Properties** window updates accordingly to show that object's properties.
|
||||
|
||||
Now that you're familiar with Studio's core UI and what they're responsible for, you can continue learning about how to create with Studio.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: Learn about Studio
|
||||
description: Learn the basics of creating an account, downloading Studio, and navigating its UI.
|
||||
next: /tutorials/curriculums/studio/create-account
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<img src="../../../assets/tutorials/landing/StudioThumbnail.png" width="90%" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
This tutorial acts as an introduction to Roblox Studio, a free application on Windows or Mac that has everything you need to start creating experiences on the Roblox platform! As you follow each chapter, you will learn how to login into Studio, then navigate its core user interface windows.
|
||||
|
||||
This lesson is intended for readers who are **brand new** to working in Studio. If you are already familiar with Studio, try another beginner lesson plan or jump straight to [building your first experience](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Create Roblox account</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [create a free account](create-account.md) on Roblox so that you can login to Studio.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Install Roblox Studio</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [install Studio](install-studio.md) on your computer.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Explore Studio UI</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn about the [fundamental windows](explore-ui.md) you need to know to navigate Studio.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 4 - Next steps</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn where you can [find more tutorials](next-steps.md) about creating experiences.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: Install Roblox Studio
|
||||
description: Explains how to install Studio on your Windows or Mac computer.
|
||||
next: /tutorials/curriculums/studio/explore-ui
|
||||
prev: /tutorials/curriculums/studio/create-account
|
||||
---
|
||||
|
||||
import StudioSystemReqs from '../../../includes/studio/system-reqs.md'
|
||||
|
||||
**Roblox Studio**, or **Studio** for short, is Roblox's free application with powerful 3D creation tools you can use to build and publish experiences to hundreds of millions of Roblox users on consoles, desktops, and mobile devices. It's available on Windows and Mac computers that meet the following system requirements:
|
||||
|
||||
<StudioSystemReqs components={props.components} />
|
||||
|
||||
Studio comes bundled with the Roblox engine that also powers experiences on the Roblox app. This means that when you develop and publish your experiences in Studio, players interact with the same engine from your development on their personal devices.
|
||||
|
||||
<Alert severity="info">
|
||||
For an overview on Studio's functionality and the Roblox engine, see [Roblox Studio](../../../studio/index.md).
|
||||
</Alert>
|
||||
|
||||
To install Roblox Studio:
|
||||
|
||||
1. Click the following **Download Studio** button.
|
||||
|
||||
<UseStudioButton variant='blueLogoIconButton' />
|
||||
|
||||
1. In the pop-up dialog, click the **Download Studio** button.
|
||||
1. Find the Studio installer in your browser's download history and double-click the file.
|
||||
|
||||
- On a Windows computer, the file is called `RobloxStudio.exe`.
|
||||
- On a Mac computer, the file is called `RobloxStudio.dmg`.
|
||||
|
||||
1. After Studio finishes installing, a pop-up confirmation displays. Click the **Launch Studio** button.
|
||||
1. After Studio opens, log in with your new Roblox account.
|
||||
|
||||
Now that you have Studio open, let's explore the fundamental UI you need to know to navigate Studio.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: Next steps
|
||||
description: Explains ways to continue learning about creating in Studio.
|
||||
next: /tutorials/curriculums/building/
|
||||
prev: /tutorials/curriculums/studio/explore-ui
|
||||
---
|
||||
|
||||
Congratulations on completing your introduction to Roblox Studio! Now that you have Studio on your computer, login credentials, and familiarity with Studio's core user interface, you are ready to start building anything you can imagine.
|
||||
|
||||
## New to development?
|
||||
|
||||
If you are brand new to development, check out the following beginner tutorials on different development areas that you can specialize in:
|
||||
|
||||
- [Building lesson](../building/index.md) — Learn how to **build a simple platformer** using one of Studio's templates.
|
||||
- [Coding lesson](../coding/index.md) — Learn how to **code an interactive story** where players replace placeholder words with their own answers.
|
||||
- [VFX lesson](../artist/index.md) — Learn how to **customize special effects art** for a pre-made carnival ride experience.
|
||||
- [Animation lesson](../animator/index.md) — Learn how to **create keyframes for various poses**, then loop them into a swimming animation that you can reuse in any experience.
|
||||
|
||||
After you complete each of these tutorials, you will be ready to [create your first experience](../core/index.md) and learn about [coding fundamentals](../../fundamentals/coding-1/landing.md) for Luau, Studio's programming language.
|
||||
|
||||
<Alert severity="info">
|
||||
Game development is a massive field with a lot of different specializations. If one development area doesn't interest you, try another!
|
||||
</Alert>
|
||||
|
||||
## Just new to Studio?
|
||||
|
||||
If you aren't new to development, but are new to Studio, check out the following guides for an overview of how experiences technically work:
|
||||
|
||||
- [Projects](../../../projects/index.md) — Learn how to create and manage projects in Studio.
|
||||
- [Assets](../../../projects/assets/index.md) — Learn about how everything in Roblox is represented as a cloud-based asset.
|
||||
- [Data model](../../../projects/data-model.md) — Learn how places look and function in your experiences.
|
||||
- [Client-server runtime](../../../projects/client-server.md) — Learn how experiences run in the Cloud.
|
||||
|
||||
After you have a solid understanding of these guides, you will be ready to examine the [Plant](../../../resources/plant-reference-project.md) reference project that focuses on common use cases that you might encounter when you develop an experience on Roblox, including tradeoffs, compromises, and the rationale of various implementation choices so that you can make the best decisions for your own experiences.
|
||||
|
||||
As you start to build your own experiences, be sure to check out the intermediate tutorials for common use cases, such as:
|
||||
|
||||
- [Create HUD meters](../../use-case-tutorials/ui/create-hud-meters.md)
|
||||
- [Add 3D audio](../../use-case-tutorials/audio/add-3D-audio.md)
|
||||
- [Save player data](../../use-case-tutorials/data-storage/save-player-data.md)
|
||||
|
||||
When you reach a point where you feel comfortable developing in Studio, you will be ready for the advanced paths that offer a deep dive into industry-standard workflows for areas like [environmental art](../environmental-art/index.md), [gameplay scripting](../gameplay-scripting/index.md), and [UI design](../user-interface-design/index.md).
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
---
|
||||
title: Choose an art style
|
||||
description: Explains how to greybox the laser tag environment using basic parts.
|
||||
next: /tutorials/curriculums/user-interface-design/wireframe-your-layouts
|
||||
prev: /tutorials/curriculums/user-interface-design/
|
||||
---
|
||||
|
||||
**Choosing an art style** is the process of planning an aesthetic direction for your UI elements. It's important to begin your design process with an art style because art styles provide visual ground rules you can reference to ensure your UI intuitively provides players information about what they can do in your experience.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) `.rbxl` file as a reference, this section of the user interface curriculum shows you how to make meaningful decisions on how to design your UI elements according to the genre of your experience, including guidance on:
|
||||
|
||||
- Identifying necessary UI elements according to what you want to communicate to players throughout your experience.
|
||||
- Selecting a color theme that reinforces genre-specific color conventions that are intuitive for your audience.
|
||||
- Outlining simple icons that are easy to decipher on any screen size.
|
||||
- Establishing a button order that matches the likelihood of different player workflows.
|
||||
- Determining a text system that ensures legibility on target devices.
|
||||
|
||||
After you complete this section, you will learn how to design the structure and flow of the information you want to communicate to players through UI within your experience.
|
||||
|
||||
<img width="90%" img src="../../../assets/tutorials/user-interface-design/Section1/Intro.jpg" alt="" />
|
||||
|
||||
## Identify your UI elements
|
||||
|
||||
The first step in choosing an art style for your UI is to identify what UI elements you need for the varying types of information you want to communicate to your audience. Doing this work at the start of your design process is crucial because it allows you to categorize UI elements by their functional purpose, make semantic decisions according to where and when players are going to interact with each UI element, and plan where you can reuse UI elements across your experience.
|
||||
|
||||
There are many different ways to brainstorm which UI elements are necessary for your gameplay requirements, but it's recommended to start with envisioning what a player needs to know as soon as they join your experience. For example, when a player opens the sample laser tag experience, they may ask themselves the following questions:
|
||||
|
||||
- What is the goal of the experience?
|
||||
- How do I know who is on my team?
|
||||
- How do I keep track of my team's points?
|
||||
- How do I select a blaster?
|
||||
- How do I know where the blaster shoots a laser?
|
||||
- If I'm on a mobile device, how do I shoot my blaster?
|
||||
- After I shoot a laser, when can I shoot again?
|
||||
- How do I know when I am starting the round?
|
||||
- How do I know when I successfully tag someone with my blaster?
|
||||
- How do I know when the enemy team successfully tags me?
|
||||
|
||||
By using these questions to understand what information is imperative for players to know to be successful, you can sort the sample laser tag experience's UI needs into three categories:
|
||||
|
||||
1. Information about the experience's objective.
|
||||
2. Information about the blaster.
|
||||
3. Information about the state of the player.
|
||||
|
||||
Sorting your experience's UI needs into categories is helpful because you can formulate your art style around each grouping to reinforce what information each category needs to teach players. For example, if you want your UI to tell players what actions they can take regarding their character's health status, you might choose an art style with UI elements that prioritize the color green and/or plus icons so players can quickly recognize their function.
|
||||
|
||||
After you sort your experience's UI needs into categories, you can create a list of the UI elements necessary to meet the requirements of each category. To demonstrate, the sample laser tag experience uses the following table of UI elements to address the previous list of potential player questions. As you work through this tutorial, the UI curriculum will continue to refer to this list and highlight major design decisions that relate to each category of UI elements.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>UI Elements</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Information about the experience's objective</td>
|
||||
<td><ul><li>Objective prompt</li><li>Team point tracker</li><li>Team indicator</li></ul></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Information about the blaster</td>
|
||||
<td><ul><li>Blaster selector</li><li>Cross hair</li><li>Hit marker</li><li>Cooldown meter</li><li>Shoot button for mobile devices</li></ul></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Information about the state of the player</td>
|
||||
<td><ul><li>Force field screen when player is joining or rejoining the round</li><li>Respawn screen when player is tagged out</li><li>Indicator when enemy players are tagged out</li></ul></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Now that you have a list of UI elements for your experience, it's time to begin making stylistic and semantic choices for each grouping of UI elements, starting with a color theme.
|
||||
|
||||
## Select a color theme
|
||||
|
||||
A **color theme**, or color palette, is a selection of colors that each communicate a message through consistent application within your experience, such as using a bright color to indicate when something is selectable. Applying a color theme to your UI elements is important, especially when you rely on color conventions within the genre of your experience, because it allows players to quickly understand your UI with minimal effort.
|
||||
|
||||
In the [Environmental Art Curriculum](../environmental-art/index.md), each half of the laser tag environment is color-coded from a top-down view to differentiate which area of the map is near each team's respective spawn zone: **mint** for the team that assembles on the left side of the map, and **carnation pink** for the team that assembles on the right side of the map. These specific colors are useful because they are **complementary**, meaning that they contrast each other well, and enable players to easily scan their surroundings and orient themselves regardless of what direction they're facing in the building.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/user-interface-design/Section1/GreenLeft.jpg" alt="The door on the mint green side of the map." />
|
||||
<figcaption>Pastel Blue-green</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/user-interface-design/Section1/PinkRight.jpg" alt="The door on the carnation pink side of the map." />
|
||||
<figcaption>Carnation Pink</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
The sample laser tag experience uses this same color theme in its UI to highlight information that pertains to each team, such as each player's team indicator within the 3D space, or the team point tracker that overlays the screen. This consistency assists players in being able to quickly understand information about the experience's objective during the fast-paced gameplay of a first-person shooter experience, especially as players need to make rapid decisions while traversing the environment.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/gameplay-scripting/Creating-Teams/Objective-UI.jpg" width="100%" alt="" />
|
||||
<figcaption>A preview of the team point tracker in the sample laser tag experience.</figcaption>
|
||||
</figure>
|
||||
|
||||
When selecting a color theme for your own experience, consider the following:
|
||||
|
||||
- The strength of a color theme relies on helping players make quick mental associations between each color and their function. For this reason, **limit your color theme** to only highlight the key information you want players to associate with your UI elements.
|
||||
- To ensure players with colorblindness can understand the message of your color theme, **don't always rely on color alone** to distinguish between UI elements. Instead, combine colors with icons, shapes, and/or animations to ensure your UI communicates effectively with every player.
|
||||
- It's more important for your UI to be legible than aesthetically pleasing. For this reason, **prioritize simple UI** with color that remains readable over both light and dark elements in the 3D world.
|
||||
|
||||
To highlight the guidance in the last point, the sample laser tag experience utilizes the neutral colors of black and white for almost every other UI element that overlays the 2D screen. Black and white contrast each other well, and they are easy to read as the rest of the screen displays an otherwise colorful 3D environment.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section1/PlayerInfoPreview.png" />
|
||||
</figure>
|
||||
|
||||
## Outline simple icons
|
||||
|
||||
An **icon** is a symbol that represents an action, object, or concept in an experience. Outlining icons that are simple and intuitive is important because the end result enables players to easily recognize what they are able to do and what you want to tell them through your UI without using text, which can clutter the screen and pull attention away from content that matters. This process is even more crucial if your audience accesses your experience using a small screen on mobile devices.
|
||||
|
||||
Simple icons ideally have a distinct style from your 3D elements while still complimenting the overall world of your experience. For example, in the final environment of the [Environmental Art Curriculum](../environmental-art/index.md), both modular and prop 3D assets have a clean, high-tech art style that utilizes rectangular forms with soft, rounded corners. From the beveled panels along the floor to the near circular windows in the ceiling, nothing includes a sharp edge.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="100%" img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SAPackages-Intro.jpg" />
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="85%" img src="../../../assets/tutorials/environmental-art-curriculum/Section3/SettingParameters-CanCollide.png" />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To complement this art style while still remaining unique, all UI elements that you will learn to make later in this tutorial include a futuristic aesthetic and round angles without matching the shape language of the 3D assets in the environment. This keeps each icon's meaning distinct from other information in both the 2D and 3D space.
|
||||
|
||||
To demonstrate this concept, see the following two images from the sample laser tag experience of the crosshair that tells players where their blaster shoots on the screen, and the button that allows players to shoot their blaster on mobile devices. Both icons include soft angles to be cohesive with the overall world, but their hexagonal and circular shapes set them apart from anything a player could associate the icons with in the experience.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img width="40%" img src="../../../assets/tutorials/user-interface-design/Section3/CrosshairIcon.jpg" />
|
||||
</figure>
|
||||
<figure>
|
||||
<img width="40%" img src="../../../assets/tutorials/user-interface-design/Section3/BlastButtonIcon-Default.jpg" />
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
When outlining simple icons for your own experience, consider the following:
|
||||
|
||||
- Simple icons are legible even when they are small. For this reason, **limit details on your icons** that would become unrecognizable on mobile device screens.
|
||||
- Icons are powerful because they can communicate a message no matter the player's language. As long as it doesn't impact player comprehension of your UI, **replace unnecessary text with icons** to improve localization efforts.
|
||||
- Many experiences of the same genre use icons that are stylistically similar, such as a sword icon that represents strength, or a beaker that represents magic. **Embrace the symbolism within your experience's genre** so players can understand your icons without additional guidance.
|
||||
|
||||
If you don't know what types of icons are common within your experience's genre, check out the [Game UI Database](https://gameuidatabase.com/). This free resource tool for UI designers includes screenshots from hundreds of experiences of different genres that you can reference during your design process.
|
||||
|
||||
## Establish an interaction order
|
||||
|
||||
An **interaction order** is the sequence of interactions players can have with your UI. As there are often multiple interactable UI elements on the screen, it's important to establish an intuitive interaction order to assist players in making decisions as they navigate various workflows.
|
||||
|
||||
There are typically three types of interactions in a workflow:
|
||||
|
||||
- **Primary Interaction** – The action a player is most likely to perform.
|
||||
- **Secondary Interaction** – The action a player is likely to perform as an alternative to the primary action.
|
||||
- **Tertiary Interaction** – The action a player is least likely to perform.
|
||||
|
||||
Each interaction type must have a different level of visual emphasis depending on the likelihood of a player performing the action. To illustrate this concept, examine the following image of the interaction order for the workflow to select a blaster in the sample laser tag experience, in which A represents the primary interaction, B represents the secondary interaction, and C represents the tertiary interaction.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section1/BlasterInteractionOrder.png" />
|
||||
</figure>
|
||||
|
||||
In this workflow, the action a player is most likely to perform is to select between the two different types of blasters, so the blaster buttons are much larger than any other interactable element in the design. This level of visual emphasis grabs the player's attention, and draws their eye to the middle of the overall UI element. After the player makes their decision, the logical next step in the workflow is to confirm their selection and start the round. For this reason, the SELECT button is directly below the primary interaction.
|
||||
|
||||
While unlikely, the player may not know they are able to select one of the blaster buttons to communicate which blaster they want to use. To assist in this situation, there are two arrow tertiary buttons the player can utilize to cycle between their choices. These buttons are subtle and much smaller than the elements of the primary and secondary interactions, but they are also perceivable to the player that needs direction on what actions they are able to perform.
|
||||
|
||||
If you were to place these primary, secondary, and tertiary interactions in a different interaction order, such as swapping the SELECT button with the left arrow button, players wouldn't have clear direction on the sequence of choices they need to make. For this reason, in addition to the visual emphasis of a button order, effective workflows follow a **visual hierarchy** that guides players through your ideal order of decisions according to the direction they are likely to scan information, such as top-down and left-to-right.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section1/BlasterFlow.png" />
|
||||
<figcaption>The success of this design is that it's natural for players familiar with both left-to-right and right-to-left languages!</figcaption>
|
||||
</figure>
|
||||
|
||||
When establishing an interaction order for the workflows in your own experience, consider the following:
|
||||
|
||||
- Players need to have a clear understanding of when they can interact with your UI to perform actions. For this reason, it's recommended to **provide at least one form of visual feedback** for interactable UI elements, such as displaying an outline or changing a button's size, color, or animation when it's in focus.
|
||||
- If labels on interactable UI elements are vague or similar to one another in the same workflow, players can misunderstand how to complete an action or process. To avoid a negative player experience, **create labels that are clear, concise, and distinct from one another**.
|
||||
- If interactive UI elements are too large, they can distract from other important information on the screen. Conversely, if they are too small, they can be hard to read or difficult to select, especially if they're in close proximity on mobile devices. For this reason, it's essential to **review the size of your interactable UI elements on various screen sizes**.
|
||||
|
||||
In [Implement in Studio](./implement-designs-in-studio.md), you will learn how to use `Class.UIAspectRatioConstraint` objects to ensure UI elements maintain a specific aspect ratio no matter what device players use to access your experience. In addition to making your design process easier, this technique can also help you meet the Web Content Accessibility Guidelines' [Touch Target Size and Spacing](https://w3c.github.io/Mobile-A11y-TF-Note/#targetSize) recommendation to create a touch zone for interactive UI elements that's at least 9x9 mm on mobile devices.
|
||||
|
||||
## Determine a text system
|
||||
|
||||
A **text system** is a set of rules about fonts and style for all of the words in your UI, such as "always bold headers" or "use green font when referencing a health stat." Determining a text system early into your design process allows you to have a structure that you can consistently apply throughout your experience so players know what to expect as they search for the information they need.
|
||||
|
||||
While text systems can vary depending on the experience's genre or 3D world requirements, the most important rule that all text systems must follow is to **ensure all of your UI text is clear and easy to read**. By using this rule as a basis for all decisions related to your text system, you can improve the accessibility and user experience for players reading your UI by considering the different ways players may interact with your text, such as:
|
||||
|
||||
- The device players may use to access your experience.
|
||||
- The language in which players may read your localized text.
|
||||
- The possible underlying background behind on-screen text.
|
||||
|
||||
For example, the following screen response design of when a player is tagged out scales to a smaller or larger font size according to a player's device, includes enough room on either side of the english text for languages with longer translations, and includes a contrasting background so players can read the text no matter what color is in their background.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section1/RespawningPreview.png" />
|
||||
</figure>
|
||||
|
||||
When determining a text system for your own experience, consider the following:
|
||||
|
||||
- Text is difficult to read when it blends in with the noise of its background. To improve the legibility of your UI, **display text on top of a contrasting color or with a stroke**.
|
||||
- If you don't scale your text for different devices, the text will either take up too much room or become small and indecipherable on the player's screen. To catch discrepancies in your text, **test your design on multiple devices** throughout the design process.
|
||||
- Words can extend beyond your original design when you localize them into other languages. To improve the composition of your design, **reference the most space your text can take up on the screen**.
|
||||
- While some fonts can fit the aesthetics of your experience, they may be difficult to read in large quantities. For this reason, **use stylized text sparingly**, such as for titles or alert text.
|
||||
|
||||
Once you have a plan for the art style of your UI, you can move on to the next section of the tutorial to learn how to wireframe the layout of each element in various player workflows.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: User interface design curriculum
|
||||
description: Learn best practices for creating user interfaces, including displaying critical information and implementing an art style.
|
||||
next: /tutorials/curriculums/user-interface-design/choose-an-art-style
|
||||
hideInPageNavigation: true
|
||||
---
|
||||
|
||||
<>
|
||||
<Grid
|
||||
alignItems="stretch"
|
||||
container
|
||||
direction="row">
|
||||
|
||||
<Grid item Large={7} XSmall={12} direction="column">
|
||||
|
||||
<div class="container"
|
||||
style={{position: "relative", paddingBottom: "56.25%", height: 0}}>
|
||||
<iframe width="880" height="495" src="https://www.youtube-nocookie.com/embed/eqW9SINYeWg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style={{position: "absolute", top: 0, left: 0, width: "95%", height: "95%"}}></iframe>
|
||||
</div>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item Large={5} XSmall={12} direction="column">
|
||||
|
||||
**User Interface Design** is the discipline of planning and implementing UI elements that either provide essential information for gameplay, or teach players how to complete unique user flows within your experiences.
|
||||
|
||||
You'll learn how to recreate high-quality UI elements for a first-person shooter laser tag experience by following step-by-step processes and best practices for following an art style, wireframing the structure and flow of UI information, and implementing UI designs within Studio.
|
||||
|
||||
This course is intended for readers who are familiar
|
||||
with general building concepts and tools in Roblox Studio. If you need help
|
||||
learning how to build an environment and navigating Studio's UI, try the [core curriculum](../core/index.md).
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</>
|
||||
|
||||
## Course contents
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 1 - Choose an art style</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [plan an aesthetic direction](choose-an-art-style.md) for your UI elements.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 2 - Wireframe your layouts</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [design the structure and flow](wireframe-your-layouts.md) of the information you want to communicate through in various workflows.
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
||||
<BaseAccordion>
|
||||
<AccordionSummary>
|
||||
<Typography variant="h4">Chapter 3 - Implement designs in Studio</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
Learn how to [implement your UI layouts](implement-designs-in-studio.md) in Studio using both built-in and custom UI elements with scripts.
|
||||
|
||||
<a href="https://www.roblox.com/games/14817965191/Laser-Tag-1A">
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="contained">
|
||||
Get the Place File
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
title: Wireframe your layouts
|
||||
description: Explains how to greybox the laser tag environment using basic parts.
|
||||
next: /tutorials/curriculums/user-interface-design/implement-designs-in-studio
|
||||
prev: /tutorials/curriculums/user-interface-design/choose-an-art-style
|
||||
---
|
||||
|
||||
**Wireframing your layouts** is the process of designing the structure and flow of the information you want to communicate through UI in various workflows. This critical step of the tutorial allows you to iterate on the arrangement of your UI elements, discover pain points, and improve the player's experience before you invest time and resources on a problematic design that doesn't work for your experience.
|
||||
|
||||
Using the [sample laser tag experience](https://www.roblox.com/games/14817965191/Laser-Tag-1A) `.rbxl` file as a reference, this section of the user interface curriculum shows you how to structure your UI elements on the screen, including guidance on:
|
||||
|
||||
- Planning a visual hierarchy for your UI that intuitively leads players to features and information they need to be successful in your experience.
|
||||
- Blocking in UI elements with basic shapes to review the composition of your UI elements alongside Roblox's core UI and touch controls.
|
||||
- Developing user flows to account for the necessary actions players to take to achieve their goals.
|
||||
|
||||
After you complete this section, you will learn how to construct and configure your UI in Studio that respects your art style and wireframes.
|
||||
|
||||
## Plan a visual hierarchy
|
||||
|
||||
A visual hierarchy is an organization structure that highlights UI elements by their order of importance. Planning a visual hierarchy for the different workflows in your experience is important because it sets players up for success in accomplishing their goals, which is especially necessary for the rapid gameplay of first-person shooter experiences in which goals change from moment to moment.
|
||||
|
||||
Effective visual hierarchies guide players through the features and information they need to meet your gameplay requirements according to where they naturally look on the screen to complete workflows. For example, the visual hierarchy of the sample laser tag experience must guide players through the following categories of UI elements as they navigate the map and tag players on the enemy team:
|
||||
|
||||
1. Information about the experience's objective.
|
||||
2. Information about the blaster.
|
||||
3. Information about the state of the player.
|
||||
|
||||
The location each of these categories have on the screen in the final design highlights their order of importance:
|
||||
|
||||
- The first category is near the **top** because it contains UI elements that have the most significance on how to win the game.
|
||||
- The second category takes up the majority of the screen space near the **middle** because it acts as the focal point to draw players' attention to the action in 3D space, and it has the most significance for playing the game.
|
||||
- The third category is near the **sides** because players can read this peripheral information without diverting their attention from the gameplay.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section2/VisualHierarchy.png" />
|
||||
</figure>
|
||||
|
||||
For some experience genres, you only need to display one category of information at a time because it's the only type of information that's important to the player during that workflow. However, for first-person shooter experiences, players need to see UI elements from multiple categories at the same time because they all provide information that the player needs to be successful, such as their team's score, the cooldown period for their blaster, and if they are tagged out of a round.
|
||||
|
||||
When planning a visual hierarchy for the workflows in your own experience, consider the following:
|
||||
|
||||
- If you group UI elements from different categories together throughout the screen, players won't know where to look for what they need. To assist players in navigating your experience, **group UI elements from the same category together**.
|
||||
- If you have too many categories on the screen at one time, players may not know what information they need to pay attention to. To avoid overwhelming players with clutter, **display UI elements contextually for different workflows**.
|
||||
- Similarly, if there are too many UI elements per category, it can distract players from understanding on how to complete an action or process. For this reason, it's important to **use your art style to provide visual emphasis for your ideal interaction orders**.
|
||||
|
||||
For a full review of the visual hierarchy for the sample laser tag experience, see the following table:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>UI Elements</th>
|
||||
<th>Visual Hierarchy</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Information about the experience's objective</td>
|
||||
<td><ul><li>Objective prompt</li><li>Team point tracker</li><li>Team indicator</li></ul></td>
|
||||
<td><ul><li>Top of the screen</li><li>Top of the screen</li><li>Above each player in the 3D space</li></ul></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Information about the blaster</td>
|
||||
<td><ul><li>Blaster selector</li><li>Cross hair</li><li>Hit marker</li><li>Cooldown meter</li><li>Shoot button for mobile devices</li></ul></td>
|
||||
<td><ul><li>Middle of the screen</li><li>Middle of the screen</li><li>Within crosshair</li><li>Over the blaster</li><li>Near the player's right thumb</li></ul></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Information about the state of the player</td>
|
||||
<td><ul><li>Force field screen when player is joining or rejoining the round</li><li>Respawn screen when player is tagged out</li><li>Indicator when enemy players are tagged out</li></ul></td>
|
||||
<td><ul><li>Sides of the screen</li><li>Sides of the screen</li><li>Above each player in the 3D space</li></ul></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="info">
|
||||
For more information on prioritizing UI elements on the screen, see [UI & UX Design](../../../production/game-design/ui-ux-design.md#prioritization).
|
||||
</Alert>
|
||||
|
||||
## Block in UI elements
|
||||
|
||||
Now that you have a visual hierarchy in mind, you can block in individual UI elements using basic shapes to review the composition of your custom UI layout alongside Roblox's core UI on multiple devices. This process allows you to see where you need to modify your design so that it's effective on all screen sizes that players can use to access your experience, such as on mobile, tablet, laptop, and TV screens.
|
||||
|
||||
Before you begin, you need to figure out how much screen space is available for your custom UI elements while factoring in Roblox's core UI. For example, by default, Roblox displays the following UI elements in every experience:
|
||||
|
||||
<Grid container spacing={0} alignItems="flex-start">
|
||||
<Grid item>
|
||||
- A list of players within the experience.
|
||||
- The character's health bar.
|
||||
- The character's backpack.
|
||||
- A chat window.
|
||||
- A capture button.
|
||||
- A popup menu of character emotes.
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../../../assets/ui/misc/CoreGui-Elements.jpg" width="550" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
You can [disable](../../../players/disable-ui.md) any of these elements that don't meet the gameplay requirements of your own experience in order to save screen space for your custom UI. For example, the sample laser tag experience doesn't require a backpack because players only have the ability to select a single blaster. However, if players could select multiple blasters at once, the experience could disable this core UI to ensure there's always room for the blaster selector when players respawn, then provide another location to select from your inventory.
|
||||
|
||||
In addition to Roblox's core UI, you must also consider the necessary screen space for Roblox's default touch controls. For example, when players access your experience using a mobile device, a virtual thumbstick displays in the bottom-left corner of the screen, and a jump button displays in the bottom-right corner of the screen. This ensures that players are always able to navigate your experience, even when they don't have access to a keyboard or controller.
|
||||
|
||||
<img src="../../../assets/ui/misc/Touch-Reserved-Zones.png" width="840" />
|
||||
|
||||
By taking into account both Roblox's core UI elements that are necessary for the gameplay of your experience, and the possibility of touch controls, you can block in your custom UI elements in **one design** that's adaptive across devices. This is important because it means you don't need to maintain separate versions of your UI as you update your experience.
|
||||
|
||||
To demonstrate this method, review the following two images of how the sample laser tag experience blocks in the custom UI elements for both mobile and PC devices. Both images include:
|
||||
|
||||
- Basic shapes to represent custom UI because it allows the design to easily go through several iterations before achieving the final design.
|
||||
- Grayscale coloring that enables the eye to follow information without the distraction of a colorful background environment
|
||||
- The open state of the chat window and list of active players and chat window to see how much space they take up on the screen.
|
||||
- Space for mobile controls even if they aren't necessary for PC devices.
|
||||
|
||||
When you design your layouts in this way while thinking about how many UI elements can exist on-screen at the same time, you are future-proofing for all potential layouts according to players' devices and workflows.
|
||||
|
||||
<Tabs>
|
||||
<TabItem key = "1" label="Mobile">
|
||||
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section2/MobileBlockIn.png" />
|
||||
|
||||
</TabItem>
|
||||
<TabItem key = "2" label="PC">
|
||||
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section2/PCBlockIn.png" />
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
When blocking in UI elements for your layouts, consider the following:
|
||||
|
||||
- Where and how you block in your UI can impact the composition of your layouts. **Aim for balance and symmetry** in the amount and size of your custom UI elements alongside Roblox's core UI.
|
||||
- Interacting with UI that are far from the bottom corners of some mobile and tablet devices is either uncomfortable or impossible. **Place interactive elements in easy-to-reach zones** near the natural resting position for thumbs.
|
||||
- When players navigate through your environment, the 3D space can become distracting from your on-screen UI. **Test your layouts against a variety of possible backgrounds** to ensure your on-screen UI elements remain clear and legible.
|
||||
|
||||
## Develop user flows
|
||||
|
||||
A user flow is a collection of paths that players can take in an experience to complete a task, such as to choose a weapon, purchase an item, or heal a character. User flows typically start where you expect players to begin the task, and finish with the final action or achievement players reach at the end of the task. In addition, effective user flows also account for unusual paths players may want to take on their own to achieve the same goal.
|
||||
|
||||
The following flow chart displays the user flow of a player entering and playing the sample laser tag experience. When a player opens the experience, they join a lobby. If the round isn't yet in progress, they wait until the round loop starts, otherwise they join a team that's currently playing the game. If the end condition hasn't been met, such as hitting a time limit or tagging out 10 players per team, then players select their blaster and experience the main gameplay of tagging or getting tagged by the enemy team, equipping a new blaster within the arena, or respawning after they get tagged out. Once players meet the end condition, they finish the round and join the lobby again.
|
||||
|
||||
<figure>
|
||||
<img src="../../../assets/tutorials/user-interface-design/Section2/UserFlowChart.jpg" />
|
||||
</figure>
|
||||
|
||||
It's important to develop user flows for everything players can do in your experience because it allows you to assess where and how you want them to complete tasks, and anticipate where you can alleviate the pain points for anyone who follows their own path. For example, if you were to develop additional user flows for the sample laser tag experience, what happens when players:
|
||||
|
||||
- Quit a round before the end condition is met?
|
||||
- Leave the experience while joining a team?
|
||||
- Receive a phone call in the middle of a round?
|
||||
- Lose their connection to the round for less than 15 seconds?
|
||||
|
||||
By visualizing all potential actions players can take or scenarios that can happen to players while completing tasks, you can better determine how the layout of your UI elements negatively impacts their experience, then make adjustments before implementing your designs in Studio. If tasks feel intuitive, unobtrusive, and convenient, players are more likely to spend time in your experience, and return at a later date.
|
||||
|
||||
When developing user flows for your own experience, consider the following:
|
||||
|
||||
- What you want players to do in your experience and what they want to do may be completely different. It's best to **test your layouts with multiple types of players** to get an understanding of the paths they want to take to achieve their goals.
|
||||
- Flow charts can become difficult to read and ineffective if they try to capture multiple scenarios at once within the diagram. To focus your attention on each task, **limit flow charts to one primary task at a time**.
|
||||
- Interaction patterns vary depending on if players are using touch controls, gamepads, or computer mice to interact with your experience. If you're designing for multiple devices, **develop unique user flows for each device** so you can identify pain points and simplify workflows.
|
||||
|
||||
After you finalize the design of your wireframes, it's time to move into Studio and make your UI come to life.
|
||||
Loading…
Add table
Add a link
Reference in a new issue