Créer un mouvement dans n'importe quel environnement dans une expérience aide à le ressentir instantanément plus immersif et réaliste dans notre monde, que ce soit grâce à la déplacement d'arbre ambiant, à la réactivation de portes du joueur ou même aux boîtes qui se déplacent lorsqu'elles se heurt
Créer la tempête
La tempête a traversé de nombreuses itérations avant que nous nous soyons mis d'accord sur ce qui est en direct dans The Mystery of Duvall Drive. Au début, nous avons pensé à la tempête comme un pilier d'obsidienne géant, et dans les itérations suivantes, nous l'avons considérée comme un portail géant vers l'espace corrompu. Après avoir expérimenté avec de nombreux types de tempêtes avec des sentiments uniques, nous avons décidé d'un type de tempête avec un "œil" central plus petit car
- La tempête devrait donner aux joueurs un sens de l'impact de cet événement sur le monde, y compris les arbres qui volent et les débris qui volent autour.
- Le vortex tourbillant des nuages eux-mêmes devrait donner aux joueurs un coup d'œil sur le portail central sans révéler tout . Cela pourrait encourager les joueurs à enquêter plus près pour voir ce qui se passe.
- Le point de lumière plus serré nous permettrait de nous concentrer sur la composition de la maison , qui est à la fois le personnage principal et l'endroit où la plupart du jeu se trouve.
Pour faire en sorte que la tempête se sente dynamique, agressive et toujours changeante dans son environnement, nous avons utilisé les systèmes et les fonctionnalités suivants :
- TweenService > - Pour le mouvement des nuages.
- Changements d'éclairage - Pour créer le cloud à l'éclair.
- Faisceaux) > - Pour la « lumière volumétrique » et les boulons de foudre.
- Émetteurs de particules > - Pour les débris volant vers le portail et volant autour en raison du vent soufflant.
- Animations > - Pour les arbres qui soufflaient dans le vent.
Ajouter des nuages avec des textures
Bien que les nuages dynamiques soient parfaits pour les nuages de haute altitude, réalistes, nous avons besoin de quelque chose qui se sente dram
Puisque chaque maillage de cloud nécessaire pour être énorme pour entourer la maison et transmettre à quel point la tempête était énorme, nous savions que nous avions besoin de tuer la texture que nous voulions utiliser sur les maillages de cloud individuels afin qu'elle soit lourdement répétée sur la surface du maillage. Nous avons testé les matériaux que nous avons faits pour le cloud sur ces simples parties, puis appliqués à la vortex !
Contrairement aux émetteurs de particules ou aux rayons, les mailles nous ont permis d'être en mesure de refléter la lumière de chaque maillage, ce qui était important lorsque nous voulions implémenter le tonnerre entre nuages. Nous avons également modélisé dans le tordant afin que le tonnerre rebondisse de celui-ci pour qu'il ressemble à s'il avait de la profondeur ! Ceci était important surtout dans les situations où les exigences de performance de l'expérience ont fait tomber les niveaux de surface de nos objets d
Missions de nuages tournants
Après avoir été satisfaits de l'apparence visuelle globale des nuages, nous avons dû le mettre en mouvement ! Nous avions les formes générales de chaque couche de nuage en emplacement, mais il a pris beaucoup de temps pour s'assurer que l'effet de rotation ressemblait bien dans la pratique. Nous avons d'abord essayé d'utiliser contraintes pour introduire la vitesse qui conduirait
Nous voulions un méthode facile à utiliser pour faire pivoter des instances qui étaient soit trop loin pour être interagibles, telles que les nuages, soit trop petites ou décoratives pour être importantes pour le
Comme dans de nombreux cas dans la démo, nous avons utilisé une balise LocalSpaceRotation pour que nous puissions gérer les instances affectées dans Studio en utilisant un plugin de balisage d'instances. Nous avons utilisé un seul LocalScript qui gère toutes les instances taguées en utilisant le plugin de balisage d'instances CollectionService afin que nous n'ayons pas à avoir une tonne de scripts à
local function Init()
for _, obj in CollectionService:GetTagged("LocalSpaceRotation") do
if obj:IsDescendantOf(workspace) then
SetupObj(obj)
end
end
end
CollectionService:GetInstanceAddedSignal("LocalSpaceRotation"):Connect(function(obj)
objInfoQueue[obj] = true
end)
CollectionService:GetInstanceRemovedSignal("LocalSpaceRotation"):Connect(function(obj)
if objInfo[obj] then
objInfo[obj] = nil
if objInfoQueue[obj] then
objInfoQueue[obj] = nil
end
end
end)
Init()
objInfo est une serveurqui a des informations pour tous les objets pertinents, tels que leur vitesse de rotation et leur axe. Notez que nous ne appelons pas immédiatement SetupObj à partir de CollectionService.GetInstanceAddedSignal
Nous avons roté les instances dans la fonction Update connectée au battement cardiaque. Nous avons obtenu le
local parentTransformif parentObj:IsA("Model") thenif not parentObj.PrimaryPart then-- la partie principale pourrait ne pas être diffusée encorecontinue -- attendez que la partie principale se répliqueendparentTransform = parentObj.PrimaryPart.CFrameelseparentTransform = parentObj.CFrameendcurObjInfo.curAngle += dT * curObjInfo.timeToAnglelocal rotatedLocalCFrame = curObjInfo.origLocalCFrame * CFrame.Angles( curObjInfo.axisMask.X * curObjInfo.curAngle, curObjInfo.axisMask.Y * curObjInfo.curAngle, curObjInfo.axisMask.Z * curObjInfo.curAngle )if obj:IsA("Model") thenobj.PrimaryPart.CFrame = parentTransform * rotatedLocalCFrameelseobj.CFrame = parentTransform * rotatedLocalCFrameend
Nous avons vérifié qu'un Model.PrimaryPart valide soit défini pour gérer le streaming. Si une mise à jour est appelée sur notre objet pendant qu'un Model.PrimaryPart (qui peut point à un maillage enfant) n'a pas été diffusé encore, nous sauterions la mise à jour. Le système
Concevoir des coups de foudre
Comme Studio n'offre pas d'outil de génération de foudre en boîte, et que le système de partie avait quelques limites qui ne fonctionneraient pas pour les coups de foudre du héros, nous avons dû être créatifs avec une solution pour les coups de foudre du héros. Nous avons décidé sur deux systèmes principaux pour faire partie des coups de foudre : des rayons texturés pour les coups de foudre du héros qui vien
Brouillons texturaux
Nous utiliserions généralement un séquenceur ou une chronométrie pour diriger le timing d'un effet de griffes de lumière comme celui-ci, mais comme Studio ne fournit pas encore cette fonctionnalité, nous avons décidé d'écrire des scripts qui contrôleraient le timing d'un effet de griffes de lumière. Le scripting de cet effet est assez simple, mais il atteint les objectifs suivants :
- Les éléments des coups de foudre, tels que leurs textures, leur brillance et leurs délais, sont aléatoires avec chaque coup.
- Les changements audio et post-FX sont en synchronisation avec les frappes FX.
- Les joueurs qui sont soit à l'intérieur, soit dans la zone corrompue ne pourraient pas les voir ou les entendre.
Nous avons un Script côté serveur qui calcule divers paramètres et délais, les envoie à tous les clients et attend un nombre aléatoire de temps :
local function LightningUpdate()
while true do
task.wait(rand:NextNumber(3.0, 10.0))
local info = CreateFXData()
lightningEvent:FireAllClients(info)
end
end
À l'intérieur de CreateFXData, nous remplissons la structure d'information afin que tous les clients obtiennent les mêmes paramètres.
Sur le côté client ( LightningVFXClient ) nous vérifions si ce client doit exécuter le FX :
local function LightningFunc(info)
…
-- pas d'effets visuels à l'intérieur
if inVolumesCheckerFunc:Invoke() then
return
end
-- pas de FX quand il n'est pas dans le monde « normal »
if not gameStateInfoFunc:Invoke("IsInNormal") then
return
end
…
En outre, nous exécutons la séquence pour définir les textures, les positions et la luminosité, nous exécutons les tweens et nous utilisons task.wait(number) . Les paramètres aléatoires sont de la structure d'information que nous avons reçue du serveur, et certains chiffres sont fixés.
beam.Texture = textures[info.textIdx]beamPart.Position = Vector3.new(info.center.X + og_center.X, og_center.Y, info.center.Y + og_center.Z)-- Effacerbeam.Brightness = 10ppCC.Brightness = maxPPBrightnessppBloom.Intensity = 1.1bottom.Position = top.PositiontweenBrightness:Play()tweenPPBrightness:Play()tweenPPBrightness:Play()tweenBottomPos:Play()tweenBrightness.Completed:Wait()-- audioif audioFolder and audioPart thenif audioFolder.Value and audioPart.Value thenaudioUtils.PlayOneShot(audioObj, audioFolder.Value, audioPart.Value)endendtask.wait(info.waitTillFlashes)-- and so on
Pour vérifier si un joueur est à l'intérieur, nous utilisons une fonction inVolumesCheckerFunc, qui couvre les volumes pré- placés dans les zones intérieures et vérifie si la position du joueur est dans l'un d'eux (PointInABox). Nous aurions pu utiliser la détection basée sur le toucher, mais nous avons constaté que lorsqu'un joueur prend place
Pour vérifier si un joueur se trouve dans des zones corrompues, nous invoquons une fonction d'aide gameStateInfoFunc, qui vérifie l'état du jeu actuel. Pour jouer un son aléatoire à partir d'un dossier, nous avons également utilisé une fonction d'aide PlayOneShot. Pour les boulons de foudre eux-mêmes, ces étaient super faciles à créer dans Photosh
Utiliser les systèmes d'émetteurs de particules
Les héros éclairs frappent sont soutenus par un système de particules qui suggère un éclair lointain en créant l'impression d'un couche de nuages dans le fond capturant la lumière d'un éclair lointain, ou un éclairage cloud-to-cloud. Nous avons atteint cet effet via un système de particules très simple qui clignote une nuage sur le períphé de nuage principal. Le système émet une nuage de particules periodiquement avec une courbe de transparence aléatoire :
Faire souffler des arbres dans le vent
Après que nous avions les nuages et le tonnerre fonctionner de la manière dont nous le voulions, nous avons ensuite dû ajouter deux autres composantes principales d'une tempête : le vent et la pluie ! Ces éléments ont présenté quelques défis, y compris le besoin de travailler dans les limites actuelles de notre système de physique et d'effets spéciaux. Par exemple, faire pousser des arbres avec des vents réels n'est pas possible
Nous savions vraiment vendre l'effet du vent et de la pluie, nous avions besoin que les arbres eux-mêmes se mouvement. Il y a quelques façons que vous pouvez le faire dans le moteur, en déplaçant des parties à l'aide de plugins qui sont publiquement disponibles, en utilisant TweenService ou en animant des modèles directement. Pour nos
Nous avons commencé en peluche plusieurs arbres du Pack de modèles Endorse - Forêt Assets. Puisque ces arbres existaient déjà, et notre expérience a eu lieu dans le Pacifique Nord-Ouest, cela nous a permis de gagner du temps au début de la création de chaque modèlisationd'arbre.
Après avoir choisi nos arbres, nous savions que nous avions besoin de les peaufiner. Peaufiner un maillage est l'acte d'ajouter des jointures (ou os) à un maillage dans une autre application de modélisation 3D, comme Blender ou Maya, puis d'appliquer l'influence à ces
Nous savions que nous voulions enregistrer du temps et réutiliser la même animations, alors nous avons construit notre premier rig et nous sommes assurés que les noms joints étaient génériques parce que nous voulions utiliser ces mêmes noms dans les rigs pour les autres arbres. Nous avons également créé un motion secondaire
Une fois que nous avions créé nos articulations/os, il était temps de créer une animation de test pour se déplacer autour de toutes les articulations et os dans Studio pour voir si elles se déplaçaient comme nous le voulions. Pour ce faire, nous avons dû importer le tree dans Studio via le paramètre Custom Rig dans le 3D Importer</
Après que nous avons été heureux avec les résultats sur cet arbre, il était temps de tester la même animation sur un arbre différent ! Nous savions déjà que c'était la même animation entre les différents grands arbres pour chaque taperd'arbre, alors nous avons juste fait en sorte que notre animation ressemble à ce qu'il devrait être généralement entre un grand arbre de Redwood et un arbre de Beechwood robuste !
Pour ce faire, nous avons pris l'arbre Beechwood de ce pack forestier et avons construit un plateformesimilaire, en utilisant le même nom exact pour les jointures. Cela a donc été l'animation que nous avions précédemment importée pouvait être appliquée à cet arbre aussi. Étant donné que les animations étaient toutes basées sur les articulations tournantes, il n'a pas importé de quel arbre !
Après avoir rig et skin le Beechwood tree, nous pouvions alors l'importer et appliquer la même animation exacte. Cela a signifié que l'itération et l'édition ne nécessitaient que d'être faits sur un seul fichier, et il a également été sauvegardé sur la performance avec moins d'animations lors de l'exécution de l'expérience.
Une fois que nous avions tous les types de scripts souhaités, nous avons créé chacun en packages pour que nous puissions continuer à modifier et à mettre à jour pendant que nous jouons plusieurs des animations autour de la zone principale de l'expérience. Étant donné qu'ils avaient un coût de performance, nous les avons utilisés judicieusement autour de la maison où l'effet était le plus précieux ! Dans le futur, comme cela devient plus performant, vous pourrez ajouter plus et plus d
Fabriquer des débris de tempête
Nous voulions que la pluie apparaisse lourde, et que le brouillard et les débris soufflent à travers les arbres. Pour ce faire, nous avons configuré quelques parties invisibles pour agir comme des volumes de particules avec des enfants émetteurs de particules immédiatement en dessous des grandes nuages de tempête. En raison du nombre de particules limité dans Studio, nous ne pou
Les particules de pluie ont utilisé une nouvelle propriété d'émetteur de particules ParticleEmitter.Squash qui vous permet de rendre une partie plus longue, ou de la faire plus volumineuse.
Pour le brouillard, le brouillard et les feuilles qui volent, il était beaucoup plus simple d'ajouter un seul volume plus important couvrant moins d'aires parce que nous n'avions pas besoin d'un ton de particules à la fois. Nous avons commencé en configurant un volume et obtenu la fréquence des particules où ils voulaient.
Après cela, nous avons fait nos textures de feuilles et de vent, et avons configuré les particules pour qu'elles tournent/se déplacent à différentes vitesses et commencent à différentes vitesses. Cela a signifié que les plus grandes particules de brouillard interagiraient plus naturellement et ne ressembleraient pas tant à une texture répétitive, surtout en raison de leur taille.
Le résultat a été une action incroyable entre les arbres se déplaçant, la fenêtre qui boume et l'éclair qui crée l'effet de la tempête autour de l'œil central de la tempête.
Configurer l'Œil de la tempête
L'œil de pierre fendu avec un noyau brillant est destiné à donner aux joueurs la première indication qu'il y a quelque chose de sinistre et d'arcane dans la maison qu'ils devraient explorer plus loin. Étant que notre scène est sombre et que l'œil est loin dans le ciel, il était important de créer une silhouette d'œil f
La distance du joueur a également signifié que nous pouvions nous fier entièrement à une carte normale pour les détails de la surface de l'œil afin que le maillage soit simplement une sphère plane ! Nous avons sculpté les détails dans un maillage élevé et boulons sa carte normale sur une sphère beaucoup plus basse afin que nous puissions obtenir tous ces détails magnifiques sans le coût de performance massif.
Afin d'ajouter un sentiment supernaturel à l'œil et d'accentuer sa présence, nous avons décidé de créer un glissement de magma brillant et fluo qui s'infiltrerait dans ses rides. Bien qu'il n'y a
Un autre défi que nous avons rencontré lors de la création de l'œil a été imposé par notre utilisation de streaming combinée avec la distance de l'œil du joueur. Don
Nous avons pu ajouter du mouvement à l'œil et ses anneaux grâce au même script que nous avons utilisé pour tourner les mailles du cloud. Pour un toucher final, nous avons décidé d'ajouter un indice
Faire le stockage étendu
L'une des choses les plus amusantes à produire étaient les espaces corrompus, dans lesquels nous pouvions subvertir les attentes des joueurs en changeant littéralement le monde autour d'eux. Par exemple, dans le puzzle du père, nous voulions imiter un moment similaire à un cauchemar où, peu importe la vitesse à laquelle vous lancer, la salle semble s'allonger. Nous avons décidé de produire une cuisine étendue qui s'éloignerait des joueurs
Nous avons configuré cela avec un simple mouvement des murs, et un layout intelligent de nos chambres qui apparaîtra sur l'un des deux côtés de la cuisine. Dans l'état normal de la chambre, la cuisine était un couloir simple, mais dans l'espace corrompu, il était en fait beaucoup plus long avec plusieurs ailes et un faux mur !
Le faux mur était un groupe de modèles que nous déplaçions au moment où les joueurs entrent dans un volume déclencheur, ce qui était une partie transparente auparavant dans le panier qui ils marcheraient à travers. Ce déclencheur a également été utilisé dans un script similaire à ceux utilisés sur toutes nos portes, ce qui a appelé le TweenService pour déplacer l'opération de tweening où les positions de départ et d'arrivée étaient pour
Puisque TweenService est un système si général, tous nos modèles de données de mur devaient contenir les mêmes composants. Par exemple, la image suivante est un exemple d'un script de porte générique qui appelle un son défini par un « value » sous le modèlisation«Grow_Wall».
Ce même script, avec quelques modifications dans l'exemple de code suivant, a également déclenché l'audio pour le mouvement de la réserve. Cela a ajouté beaucoup au mouvement !
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local model = script.Parent
local sound = model.Sound.Value
local trigger = model.Trigger
local left = model.TargetL_Closed
local right = model.TargetR_Closed
local tweenInfo = TweenInfo.new(
model.Speed.Value, --Temps/vitesse de la porte Tween
Enum.EasingStyle.Quart, --Style d'atténuation
Enum.EasingDirection.InOut, --Direction de l'aération
0, --Répéter le compte
false, --Inverse vrai
0 --Délai
)
local DoorState = {
["Closed"] = 1,
["Opening"] = 2,
["Open"] = 3,
["Closing"] = 4,
}
local doorState = DoorState.Closed
local playersNear = {}
local tweenL = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Open.CFrame})
local tweenR = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Open.CFrame})
local tweenLClose = TweenService:Create(left, tweenInfo, {CFrame = model.TargetL_Closed.CFrame})
local tweenRClose = TweenService:Create(right, tweenInfo, {CFrame = model.TargetR_Closed.CFrame})
local function StartOpening()
doorState = DoorState.Opening
sound:Play()
tweenL:Play()
tweenR:Play()
end
local function StartClosing()
doorState = DoorState.Closing
--modèlisation["Door"] : Jouer()
tweenLClose:Play()
tweenRClose:Play()
end
local function tweenOpenCompleted(playbackState)
if next(playersNear) == nil then
StartClosing()
else
doorState = DoorState.Open
end
end
local function tweenCloseCompleted(playbackState)
if next(playersNear) ~= nil then
StartOpening()
else
doorState = DoorState.Closed
end
end
tweenL.Completed:Connect(tweenOpenCompleted)
tweenLClose.Completed:Connect(tweenCloseCompleted)
local function touched(otherPart)
if otherPart.Name == "HumanoidRootPart" then
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player then
--imprimer ("触摸")
playersNear[player] = 1
if doorState == DoorState.Closed then
StartOpening()
end
end
end
end
Une fois que nous avions le mur faux se déplaçant à l'arrière de la pièce, nous avions besoin du reste du contenu pour se déplacer avec lui. Pour le faire, nous avions besoin de tous les objets lâchés sur le pantry pour être soudeurés sur le mur à mesure qu'il se déplaçait. En utilisant Weld Constraints, nous pouvions rapidement souder tous les objets sur le pantry pour les déplacer en
Faire la cabane arboricole corrompue
Studio est un moteur physiquement basé que vous pouvez utiliser pour créer tout, depuis un portail balançant à une plateformetournante. Avec notre démo, nous voulions utiliser la physique pour créer un sens de réalisme dans un ensemble d'environnements autrefois irréalistes. En utilisant quelques contraintes , vous pouvez créer des courses d'obstacles amusantes et difficiles dans vos propres expériences !
Conaintes sont un groupe de moteurs physiquement bas qui aligne les objets et limite les comportements. Par exemple, vous pouvez utiliser une contrainte de tige pour vous connecter aux objets afin de les garder à une distance fixe l'un
Une fois les joueurs descendus dans la zone principale du puzzle, ils ont été accueillis par un panorama familier sur Roblox : une course d'obstacles. Ce parcours d'obstacles en particular était composé de plusieurs plates-formes tournantes et de murs tournants, ainsi que d'« zones de sécurité » qui progressaient l'histoire. Nous nous concentrerons sur les éléments tournants/tournants.
Pourquoi avons-nous utilisé des contraintes ici ? Parce que TweenService ou d'autres méthodes ne déplaceraient pas le joueur tant qu'ils étaient sur eux. Sans que l'objet déplaçant le joueur, quelqu'un pourrait sauter sur une plate-forme et il tournerait à partir d'eux. Au lieu de c
Pour ce faire, nous avons d'abord utilisé des ressources de notre kit actuel et ajouté n'importe quel contenu visuel pour un effet visuel. Nous avons fait quelques murs et plateformes incomplètes avec des trous dedans pour raconter l'histoire de la grand-mère qui a construit la cabane dans un arbre. Étant donné que nous ne voulions pas créer un tas de pièces de base et de pièces de railing uniques, nous avons créé 4 pièces de base
Nous savions que puisque nous utilisions des contraintes, nous ne pourrions pas ancrer ces mailles car elles ne bougeaient pas même avec la présence d'une contrainte/moteur les menant. La
Il était maintenant temps de configurer le comportement actuel de la contrainte de l'articulation, et d'ajouter les accessoires qui agiraient comme l'orientation de la pièce et la contrainte ensemble. Nous avons placé le faisceau de rotation sur le Motor_Turn, qui les pièces de marche étaient soudures à, et
Pour garder les plates-formes tournant à une vitesse constante, nous avons ensuite configuré les propriétés HingeConstraint.AngularVelocity, HingeConstraint.MotorMaxAcceleration, et HingeConstraint.MotorMaxTorque à des valeurs qui permettraient le déplacement et empêcheraient l'interruption si un joueur sautait dessus.
Maintenant, nous avions besoin de faire les murs pivotants. Les murs avaient besoin de pivoter sur leur centre apparent, et nous savions que nous voulions qu'ils puissent gérer n'importe quelle orientation par rapport au reste du niveau. Comme les plates-formes, nous avons construit ces murs pour qu'ils soient tous ancrés et soudeurs au Motor_Turn.
Nous avons utilisé Texture objets au-dessus de SurfaceAppearance objets pour ajouter quelques modifications à nos matériaux de base. Les textures
Une fois que nous avons testé quelques plates-formes et des murs tournants, nous avons fait plusieurs variantes et joué avec leur placement pour nous assurer que le parcours d'obstacles était difficile, captivant et aussi clair où le joueur avait besoin d'aller ! Il a fallu beaucoup de réglage sur leurs valeurs et leurs positions pour les mettre en œuvre correctement. Nous avons eu plusieurs points où les plates-formes et les murs se heurtaient l'un à l'autre
Si vous n'êtes pas sûr de ce que vos objets physiques touchent, vous pouvez activer la fidélité de la collision dans le widget Options de visualisation dans le coin supérieur droit de la fenêtre de jeu3D.
Comme vous pouvez le voir, les portes/fenêtres sont visibles, mais les détails plus petits comme les sub-panneaux ne le sont pas. C'est parce que la propriété CollisionFidelity pour les murs a été réglée sur Boîte. Nous n'avions pas besoin de la précision pour ces panneaux, donc pour économiser sur le coût des performances, c