Skip to content

Instantly share code, notes, and snippets.

@Frooxius
Created August 9, 2018 01:06
Show Gist options
  • Save Frooxius/8a326a8063b054cef7d0070ebb5057ff to your computer and use it in GitHub Desktop.
Save Frooxius/8a326a8063b054cef7d0070ebb5057ff to your computer and use it in GitHub Desktop.
Neos Basic Tutorial
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;
}
}
}
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);
}
}
}
}
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);
}
}
}
}
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;
}
}
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