Created
August 9, 2018 01:06
-
-
Save Frooxius/8a326a8063b054cef7d0070ebb5057ff to your computer and use it in GitHub Desktop.
Neos Basic Tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using BaseX; | |
namespace FrooxEngine | |
{ | |
[Category("Utility")] | |
public class BasicTutorialController : Component | |
{ | |
public const float HEIGHT = 1.2f; | |
public readonly Sync<int> ItemValidState; | |
readonly Sync<bool> _tutorialCompleted; | |
readonly SlotCleanupRef<VideoPlayer> _videoPlayer; | |
readonly SyncRef<NeosButton> _toggleButton; | |
void IncrementItemState() => ItemValidState.Value++; | |
Coroutine _currentVideoCoroutine; | |
Coroutine _tutorialCoroutine; | |
bool IsTutorialRunning => !_tutorialCoroutine.IsNull && !_tutorialCoroutine.IsDone; | |
protected override void OnAttach() | |
{ | |
base.OnAttach(); | |
var buttonSlot = World.AddSlot("Button"); | |
buttonSlot.AttachComponent<Grabbable>(); | |
buttonSlot.GlobalPosition = float3.Up * HEIGHT * 1f + float3.Forward * 0.5f + float3.Right * 0.5f; | |
buttonSlot.GlobalRotation = floatQ.AxisAngle(float3.Up, 45f) * floatQ.AxisAngle(float3.Right, 45f); | |
buttonSlot.Scale_Field.TweenFrom(float3.Zero, 0.2f); | |
var button = buttonSlot.AttachComponent<NeosButton>(); | |
button.Label = "Start Tutorial"; | |
button.Pressed.Target = ToggleTutorial; | |
_toggleButton.Target = button; | |
} | |
protected override void OnStart() | |
{ | |
base.OnStart(); | |
/*if (!_tutorialCompleted.Value) | |
RunInSeconds(6f, () => StartTutorial());*/ | |
} | |
LabelData SpawnLabel(Slot target, float3 point, Alignment align, string text, float size = 0.1f) | |
{ | |
var globalPoint = target.LocalPointToGlobal(point); | |
var label = LabelPointerMesh.SetupLabel(target, globalPoint, | |
globalPoint + point.Normalized * size * 2, out PBS_Metallic material); | |
material.AlbedoColor.Value = color.Black; | |
label.SetupFloaty(); | |
var textRenderer = label.SetupText(text, 1f, color.White, false, size, color.Black); | |
textRenderer.Align = align; | |
label.mesh.DualSided.Value = true; | |
label.mesh.Width.Value = 0.01f; | |
label.mesh.ExpandLerp.TweenFromTo(0f, 1f, 0.4f); | |
textRenderer.Color_Field.TweenFrom(color.Clear, 0.4f); | |
return label; | |
} | |
void ToggleTutorial(IButton button = null) | |
{ | |
if (IsTutorialRunning) | |
StopTutorial(); | |
else | |
StartTutorial(); | |
} | |
void StartTutorial() | |
{ | |
if (!IsTutorialRunning) | |
{ | |
_tutorialCoroutine = StartCoroutine(RunTutorialCo()); | |
if (_toggleButton.Target != null) | |
_toggleButton.Target.Label = "Stop Tutorial"; | |
} | |
} | |
void StopTutorial() | |
{ | |
StopAllCoroutines(); | |
CleanupTutorial(); | |
_tutorialCompleted.Value = true; | |
if(_toggleButton.Target != null) | |
_toggleButton.Target.Label = "Start Tutorial"; | |
} | |
IEnumerator<Context> RunTutorialCo() | |
{ | |
CleanupTutorial(); | |
var videoPlayerSlot = SpawnSlot(float3.Up * 1.5f + float3.Forward * 2f + float3.Right * 2.5f, "VideoPlayer"); | |
var videoPlayer = videoPlayerSlot.AttachComponent<VideoPlayer>(); | |
videoPlayerSlot.LocalScale = float3.One * 3f; | |
videoPlayerSlot.PointAtUserHead(float3.Backward); | |
videoPlayerSlot.GetComponentInChildren<Grabbable>().Destroy(); | |
videoPlayerSlot.Scale_Field.TweenFrom(float3.Zero, 0.5f); | |
_videoPlayer.Target = videoPlayer; | |
IncrementItemState(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.GrabCube, true); | |
// Spawn the first cube and wait for it to be grabbed | |
var cube = SpawnCube(float3.Up * HEIGHT, color.Yellow); | |
yield return Context.WaitForSeconds(1f); | |
SpawnLabel(cube.Slot, (float3.Up + float3.Left) * 0.05f, Alignment.BottomRight, "Grab the cube"); | |
while (!cube.Grabbable.IsGrabbed) | |
yield return Context.WaitForNextUpdate(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.PlaceCubeIntoSphere, true); | |
// spawn the target sphere | |
var sphere = SpawnSphere(float3.Up * HEIGHT + float3.Forward * 0.3f, color.Yellow); | |
yield return Context.WaitForSeconds(1f); | |
SpawnLabel(sphere.Slot, (float3.Up + float3.Right) * 0.1f, Alignment.BottomLeft, "Place here"); | |
while (sphere.SnappedCube == null) | |
yield return Context.WaitForNextUpdate(); | |
// Make the user teleport for a distant cube | |
yield return Context.WaitForSeconds(0.25f); | |
IncrementItemState(); | |
yield return Context.WaitForSeconds(0.25f); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.MovingAround, true); | |
sphere = SpawnSphere(float3.Up * HEIGHT, color.Cyan); | |
cube = SpawnCube(float3.Up * HEIGHT + float3.Forward * 3f, color.Cyan); | |
yield return Context.WaitForSeconds(1f); | |
SpawnLabel(cube.Slot, (float3.Up + float3.Left) * 0.05f, Alignment.BottomRight, "Bring this cube", 0.25f); | |
while (sphere.SnappedCube == null) | |
yield return Context.WaitForNextUpdate(); | |
// Make the user duplicate the cube to make two | |
yield return Context.WaitForSeconds(0.25f); | |
IncrementItemState(); | |
yield return Context.WaitForSeconds(0.25f); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.ContextualMenu, true); | |
sphere = SpawnSphere(float3.Up * HEIGHT + float3.Left * 0.5f + float3.Forward * 0.3f, color.Green); | |
var otherSphere = SpawnSphere(float3.Up * HEIGHT + float3.Right * 0.5f + float3.Forward * 0.3f, color.Green); | |
cube = SpawnCube(float3.Up * HEIGHT + float3.Forward * 0.3f, color.Green); | |
yield return Context.WaitForSeconds(1f); | |
SpawnLabel(cube.Slot, (float3.Up + float3.Left) * 0.05f, Alignment.BottomRight, "Duplicate this"); | |
while (sphere.SnappedCube == null || otherSphere.SnappedCube == null) | |
yield return Context.WaitForNextUpdate(); | |
// Make user activate a cube with the tool | |
yield return Context.WaitForSeconds(0.25f); | |
IncrementItemState(); | |
yield return Context.WaitForSeconds(0.25f); | |
cube = SpawnCube(float3.Up * HEIGHT + float3.Forward * 0.5f, color.Red); | |
var tool = SpawnTool(float3.Up * HEIGHT, color.Red); | |
yield return Context.WaitForSeconds(1f); | |
var label = SpawnLabel(tool.Slot, (float3.Up + float3.Right + float3.Forward) * 0.01f, Alignment.BottomLeft, "Equip tool"); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.EquippingTools, true); | |
while (!tool.IsEquipped) | |
yield return Context.WaitForNextUpdate(); | |
label.labelPoint.Destroy(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.UsingTool, true); | |
SpawnLabel(cube.Slot, (float3.Up + float3.Left) * 0.05f, Alignment.BottomRight, "Use the tool"); | |
while (!cube.Activated) | |
yield return Context.WaitForNextUpdate(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.SecondaryAction, true); | |
// wait for them to deactivate the cube | |
while (cube.Activated) | |
yield return Context.WaitForNextUpdate(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.UnequipTool, true); | |
label = SpawnLabel(tool.Slot, (float3.Up + float3.Left + float3.Forward) * 0.01f, Alignment.BottomRight, "Unequip tool"); | |
// Wait for them to dequip the tool | |
while (tool.IsEquipped) | |
yield return Context.WaitForNextUpdate(); | |
// destroy the tooltip component, so it can't be used anymore | |
tool.Destroy(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.ScaleTheCube, true); | |
yield return Context.WaitForSeconds(1f); | |
IncrementItemState(); | |
yield return Context.WaitForSeconds(0.25f); | |
// make a scalable cube | |
cube = SpawnCube(float3.Up * HEIGHT, color.Purple); | |
cube.Grabbable.Scalable = true; | |
yield return Context.WaitForSeconds(1f); | |
label = SpawnLabel(cube.Slot, (float3.Up + float3.Left) * 0.05f, Alignment.BottomRight, "Scale up until it's yellow"); | |
while (cube.Grabbable.IsGrabbed || cube.TargetScaleDistance > 0.01f) | |
{ | |
if (cube.TargetScaleDistance > 0.01f) | |
cube.SetColor(MathX.Lerp(color.Orange, color.Purple, cube.TargetScaleDistance * 0.7f)); | |
else | |
cube.SetColor(color.Orange); | |
yield return Context.WaitForNextUpdate(); | |
} | |
label.labelPoint.Destroy(); | |
cube.Grabbable.Scalable = false; | |
PlayVideo(NeosAssets.Neos.IntroTutorial.ClickingUIElements, true); | |
// spawn a button | |
var buttonSlot = World.AddSlot("Button"); | |
buttonSlot.GlobalPosition = cube.Slot.GlobalPosition + float3.Up * 0.25f; | |
var buttonItem = buttonSlot.AttachComponent<BasicTutorialItem>(); | |
SetupItem(buttonItem); | |
var button = buttonSlot.AttachComponent<NeosButton>(); | |
button.Label = "Open"; | |
button.Pressed.Target = cube.OpenButtonPressed; | |
while (!cube.Opened) | |
yield return Context.WaitForNextUpdate(); | |
PlayVideo(NeosAssets.Neos.IntroTutorial.Outro, true); | |
// spawn the world orb inside | |
var orbSlot = World.AddSlot("WorldOrb"); | |
orbSlot.GlobalPosition = cube.Slot.GlobalPosition; | |
var orb = orbSlot.AttachComponent<WorldOrb>(); | |
orb.URL = new Uri("neosrec:///G-Neos/R-Hub"); | |
// TODO!!! Block interaction | |
yield return Context.WaitForSeconds(2f); | |
IncrementItemState(); | |
while(!_videoPlayer.Target.IsReady) | |
yield return Context.WaitForNextUpdate(); | |
// wait for it to actually play first | |
while (!_videoPlayer.Target.IsPlaying) | |
yield return Context.WaitForNextUpdate(); | |
while (_videoPlayer.Target.IsPlaying) | |
yield return Context.WaitForNextUpdate(); | |
// spawn button to run the tutorial again | |
SpawnLabel(orbSlot, (float3.Up + float3.Right) * 0.07f, Alignment.BottomLeft, "Visit the content hub"); | |
StopTutorial(); | |
} | |
void CleanupTutorial() | |
{ | |
IncrementItemState(); | |
if(_videoPlayer.Target?.Slot != null) | |
_videoPlayer.Target.Slot.Scale_Field.TweenTo(float3.Zero, 0.5f, onDone: _videoPlayer.Target.Slot.Destroy); | |
} | |
void PlayVideo(Uri url, bool repeat) | |
{ | |
if(!_currentVideoCoroutine.IsNull) | |
_currentVideoCoroutine.Stop(); | |
_videoPlayer.Target.VideoURL = url; | |
_currentVideoCoroutine = StartCoroutine(PlayVideoCo(repeat)); | |
} | |
IEnumerator<Context> PlayVideoCo(bool repeat) | |
{ | |
while (!_videoPlayer.Target.Video.IsAssetAvailable) | |
yield return Context.WaitForNextUpdate(); | |
_videoPlayer.Target.Video.Volume.Value = 0.5f; | |
for (; ; ) | |
{ | |
if(!_videoPlayer.Target.IsPlaying) | |
_videoPlayer.Target.Play(); | |
if (!repeat) | |
break; | |
while (_videoPlayer.Target.IsPlaying) | |
yield return Context.WaitForNextUpdate(); | |
yield return Context.WaitForSeconds(10); | |
} | |
} | |
void SetupItem(BasicTutorialItem item) | |
{ | |
item.ItemValidState.Value = ItemValidState; | |
item.Controller.Target = this; | |
} | |
Slot SpawnSlot(float3 point, string name) | |
{ | |
var slot = World.AddSlot(name); | |
slot.GlobalPosition = Slot.LocalPointToGlobal(point); | |
return slot; | |
} | |
BasicTutorialTool SpawnTool(float3 point, color c) | |
{ | |
var slot = SpawnSlot(point, "TutorialTool"); | |
var tool = slot.AttachComponent<BasicTutorialTool>(); | |
var item = slot.AttachComponent<BasicTutorialItem>(); | |
SetupItem(item); | |
tool.SetColor(c); | |
return tool; | |
} | |
BasicTutorialCube SpawnCube(float3 point, color c) | |
{ | |
var slot = SpawnSlot(point, "TutorialCube"); | |
var cube = slot.AttachComponent<BasicTutorialCube>(); | |
SetupItem(cube); | |
cube.SetColor(c); | |
return cube; | |
} | |
BasicTutorialSphere SpawnSphere(float3 point, color c) | |
{ | |
var slot = SpawnSlot(point, "TutorialSphere"); | |
var sphere = slot.AttachComponent<BasicTutorialSphere>(); | |
SetupItem(sphere); | |
sphere.ItemValidState.Value = ItemValidState; | |
sphere.Controller.Target = this; | |
sphere.SetColor(c); | |
return sphere; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using BaseX; | |
namespace FrooxEngine | |
{ | |
public class BasicTutorialCube : BasicTutorialItem | |
{ | |
public const float TargetScale = 3f; | |
public const float TargetScaleTolerance = 0.25f; | |
public Grabbable Grabbable => _grabbable; | |
public bool Activated | |
{ | |
get => _activated; | |
set | |
{ | |
_activated.Value = value; | |
SetMaterial(pbr => | |
{ | |
if (value) | |
pbr.EmissiveColor.TweenTo(MathX.Lerp(pbr.AlbedoColor, color.White, 0.5f), 0.25f); | |
else | |
pbr.EmissiveColor.TweenTo(color.Black, 0.25f); | |
}); | |
} | |
} | |
public bool Opened => _opened.Value; | |
public float TargetScaleDistance | |
{ | |
get | |
{ | |
var scale = Slot.GlobalScale.x; | |
var dist = MathX.Abs(3 - scale); | |
dist = MathX.Clamp01(dist - 0.25f); | |
return dist; | |
} | |
} | |
readonly SyncRef<Grabbable> _grabbable; | |
readonly Sync<bool> _activated; | |
readonly Sync<bool> _opened; | |
readonly SyncRef<UnwrappableBoxDriver> _unwrap; | |
public void SetColor(color c) => SetMaterial(pbr => | |
{ | |
pbr.AlbedoColor.Value = MathX.Lerp(c, color.White, 0.2f); | |
pbr.Smoothness.Value = 1f; | |
pbr.Metallic.Value = 0f; | |
}); | |
public void SetMaterial(Action<PBS_Metallic> setter) | |
{ | |
var materials = Pool.BorrowList<PBS_Metallic>(); | |
Slot.GetComponentsInChildren(materials); | |
foreach (var m in materials) | |
setter(m); | |
Pool.Return(materials); | |
} | |
protected override void OnAttach() | |
{ | |
base.OnAttach(); | |
// can't destroy it | |
Slot.AttachComponent<DestroyBlock>(); | |
_grabbable.Target = Slot.AttachComponent<Grabbable>(); | |
var box = Slot.AddSlot("Box"); | |
var boxDriver = box.AttachComponent<UnwrappableBoxDriver>(); | |
boxDriver.SideSize.Value = 0.1f; | |
box.LocalPosition = float3.Down * boxDriver.SideSize * 0.5f; | |
_unwrap.Target = boxDriver; | |
Slot.AttachComponent<Snapper>(); | |
} | |
protected override void OnCommonUpdate() | |
{ | |
base.OnCommonUpdate(); | |
} | |
internal void OpenButtonPressed(IButton button) | |
{ | |
if(!Opened) | |
{ | |
Grabbable.Destroy(); | |
_opened.Value = true; | |
_unwrap.Target.Unwrap.TweenTo(1f, 2f); | |
Slot.Rotation_Field.TweenTo(floatQ.Identity, 2f); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using BaseX; | |
namespace FrooxEngine | |
{ | |
public class BasicTutorialItem : Component | |
{ | |
public readonly SyncRef<BasicTutorialController> Controller; | |
public readonly Sync<int> ItemValidState; | |
bool destroyLaunched; | |
protected override void OnAttach() | |
{ | |
base.OnAttach(); | |
Slot.LocalScale = float3.Zero; | |
Slot.Scale_Field.TweenTo(float3.One, 0.25f); | |
} | |
protected override void OnCommonUpdate() | |
{ | |
if (Controller.Target == null || Controller.Target.ItemValidState.Value != ItemValidState.Value && !destroyLaunched) | |
{ | |
destroyLaunched = true; | |
Slot.Scale_Field.TweenTo(float3.Zero, 0.25f, onDone: Slot.Destroy); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using BaseX; | |
namespace FrooxEngine | |
{ | |
public class BasicTutorialSphere : BasicTutorialItem | |
{ | |
public const float RADIUS = 0.2f; | |
readonly SyncRef<SnapTarget> _snapTarget; | |
public BasicTutorialCube SnappedCube => _snapTarget.Target.SnappedChild?.Slot.GetComponent<BasicTutorialCube>(); | |
public void SetColor(color c) => Slot.ForeachComponentInChildren<PBS_RimMetallic>(pbs => | |
{ | |
pbs.AlbedoColor.Value = c.SetA(0.2f); | |
pbs.RimColor.Value = c; | |
pbs.Transparent.Value = true; | |
pbs.Metallic.Value = 0.1f; | |
pbs.Smoothness.Value = 0.8f; | |
}); | |
protected override void OnAttach() | |
{ | |
base.OnAttach(); | |
var model = Slot.AttachMesh<IcoSphereMesh, PBS_RimMetallic>(); | |
model.mesh.Subdivisions.Value = 1; | |
model.mesh.FlatShading.Value = true; | |
model.mesh.Radius.Value = 0.2f; | |
var snapTarget = Slot.AttachComponent<SnapTarget>(); | |
snapTarget.MaximumSnapDistance.Value = RADIUS * 0.8f; | |
snapTarget.Filters.Add().Target = CanSnap; | |
_snapTarget.Target = snapTarget; | |
} | |
protected override void OnCommonUpdate() | |
{ | |
base.OnCommonUpdate(); | |
} | |
bool CanSnap(Snapper snapper, SnapTarget snapTarget) => snapper.Slot.GetComponent<BasicTutorialCube>() != null; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using BaseX; | |
namespace FrooxEngine | |
{ | |
public class BasicTutorialTool : ToolTip | |
{ | |
public override float3 LocalTip => float3.Forward * 0.075f; | |
public override bool IsTipPointing => true; | |
public override ToolTipTouchType TouchType => ToolTipTouchType.Proximity; | |
protected override void OnAttach() | |
{ | |
base.OnAttach(); | |
var visual = Slot.AddSlot("Visual"); | |
visual.AttachComponent<SphereCollider>().Radius.Value = 0.02f; | |
visual.LocalRotation = floatQ.Euler(90, 0, 0); | |
visual.LocalPosition += float3.Forward * 0.05f; | |
var material = visual.AttachComponent<PBS_RimMetallic>(); | |
var cone = visual.AttachMesh<ConeMesh>(material); | |
cone.RadiusTop.Value = 0.0025f; | |
cone.RadiusBase.Value = 0.015f; | |
cone.Height.Value = 0.05f; | |
} | |
public void SetColor(color c) => Slot.ForeachComponentInChildren<PBS_RimMetallic>(pbs => | |
{ | |
pbs.AlbedoColor.Value = c.SetA(0.2f); | |
pbs.RimColor.Value = c; | |
pbs.Transparent.Value = true; | |
pbs.Metallic.Value = 0.8f; | |
pbs.Smoothness.Value = 0.8f; | |
}); | |
BasicTutorialCube RaycastCube() | |
{ | |
var hits = Physics.RaycastAll(Tip, Slot.Forward); | |
if (hits.Count > 0) | |
{ | |
var hit = hits[0]; | |
var cube = hit.Collider.Slot.GetComponentInParents<BasicTutorialCube>(); | |
return cube; | |
} | |
return null; | |
} | |
public override void OnPrimaryPress() | |
{ | |
var cube = RaycastCube(); | |
if (cube != null) | |
cube.Activated = true; | |
} | |
public override void OnSecondaryPress() | |
{ | |
var cube = RaycastCube(); | |
if (cube != null) | |
cube.Activated = false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment