一、框架视图
二、关键代码
XRExclusiveSocketInteractor
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
/// <summary>
/// Subclass of the classic Socket Interactor from the Interaction toolkit that will only accept object with the right
/// SocketTarget
/// </summary>
public class XRExclusiveSocketInteractor : XRSocketInteractor
{
public string AcceptedType;
public override bool CanSelect(XRBaseInteractable interactable)
{
SocketTarget socketTarget = interactable.GetComponent<SocketTarget>();
if (socketTarget == null)
return false;
return base.CanSelect(interactable) && (socketTarget.SocketType == AcceptedType);
}
public override bool CanHover(XRBaseInteractable interactable)
{
return CanSelect(interactable);
}
}
CauldronContent
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.VFX;
using Random = UnityEngine.Random;
/// <summary>
/// Handle all operations related to dropping thing in the cauldron and brewing things.
/// </summary>
public class CauldronContent : MonoBehaviour
{
[System.Serializable]
public class Recipe
{
public string name;
public string[] ingredients;
public int temperature;
public int rotation;
}
[System.Serializable]
public class BrewEvent : UnityEvent<Recipe> { };
public Recipe[] Recipes;
public int TemperatureIncrement;
[Header("Effects")]
public GameObject SplashEffect;
public Animator CauldronAnimator;
private VisualEffect splashVFX;
public VisualEffect brewEffect;
/// <summary>
/// Will be called when the cauldron finish brewing, with the recipe as parameters or null if no recipe match.
/// </summary>
public BrewEvent OnBrew;
[Header("Audio")]
public AudioSource AmbientSoundSource;
public AudioSource BrewingSoundSource;
public AudioClip[] SplashClips;
bool m_CanBrew = false;
List<string> m_CurrentIngredientsIn = new List<string>();
int m_Temperature = 0;
int m_Rotation = -1;
float m_StartingVolume;
private CauldronEffects m_CauldronEffect;
private void Start()
{
m_CauldronEffect = GetComponent<CauldronEffects>();
splashVFX = SplashEffect.GetComponent<VisualEffect>();
m_StartingVolume = AmbientSoundSource.volume;
AmbientSoundSource.volume = m_StartingVolume * 0.2f;
}
void OnTriggerEnter(Collider other)
{
CauldronIngredient ingredient = other.attachedRigidbody.GetComponentInChildren<CauldronIngredient>();
Vector3 contactPosition = other.attachedRigidbody.gameObject.transform.position;
contactPosition.y = gameObject.transform.position.y;
SplashEffect.transform.position = contactPosition;
SFXPlayer.Instance.PlaySFX(SplashClips[Random.Range(0, SplashClips.Length)], contactPosition, new SFXPlayer.PlayParameters()
{
Pitch = Random.Range(0.8f, 1.2f),
SourceID = 17624,
Volume = 1.0f
}, 0.2f, true);
splashVFX.Play();
RespawnableObject respawnableObject = ingredient;
if (ingredient != null)
{
m_CurrentIngredientsIn.Add(ingredient.IngredientType);
}
else
{
//added an object that is not an ingredient, it will make automatically fail any recipe
m_CurrentIngredientsIn.Add("INVALID");
respawnableObject = other.attachedRigidbody.GetComponentInChildren<RespawnableObject>();
}
if (respawnableObject != null)
{
respawnableObject.Respawn();
}
else
{
Destroy(other.attachedRigidbody.gameObject, 0.5f);
}
}
public void ChangeTemperature(int step)
{
m_Temperature = TemperatureIncrement * step;
m_CauldronEffect.SetBubbleIntensity(step);
}
public void ChangeRotation(int step)
{
m_Rotation = step - 1;
m_CauldronEffect.SetRotationSpeed(m_Rotation);
}
public void Brew()
{
if(!m_CanBrew)
return;
brewEffect.SendEvent("StartLongSpawn");
CauldronAnimator.SetTrigger("Brew");
Recipe recipeBewed = null;
foreach (Recipe recipe in Recipes)
{
if(recipe.temperature != m_Temperature || recipe.rotation != m_Rotation)
continue;
List<string> copyOfIngredient = new List<string>(m_CurrentIngredientsIn);
int ingredientCount = 0;
foreach (var ing in recipe.ingredients)
{
if (copyOfIngredient.Contains(ing))
{
ingredientCount += 1;
copyOfIngredient.Remove(ing);
}
}
if (ingredientCount == recipe.ingredients.Length)
{
recipeBewed = recipe;
break;
}
}
ResetCauldron();
StartCoroutine(WaitForBrewCoroutine(recipeBewed));
}
IEnumerator WaitForBrewCoroutine(Recipe recipe)
{
BrewingSoundSource.Play();
AmbientSoundSource.volume = m_StartingVolume * 0.2f;
m_CanBrew = false;
yield return new WaitForSeconds(3.0f);
brewEffect.SendEvent("EndLongSpawn");
CauldronAnimator.SetTrigger("Open");
BrewingSoundSource.Stop();
OnBrew.Invoke(recipe);
m_CanBrew = true;
AmbientSoundSource.volume = m_StartingVolume;
}
void ResetCauldron()
{
m_CurrentIngredientsIn.Clear();
}
public void Open()
{
CauldronAnimator.SetTrigger("Open");
m_CanBrew = true;
AmbientSoundSource.volume = m_StartingVolume;
}
}
EndingSequence
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Serialization;
public class EndingSequence : MonoBehaviour
{
[FormerlySerializedAs("meshRenderer")]
public MeshRenderer MeshRenderer;
MaterialPropertyBlock m_Block;
Color m_ColorSphere;
// Start is called before the first frame update
void Start()
{
m_ColorSphere = new Color(1,1,1,0);
m_Block = new MaterialPropertyBlock();
m_Block.SetColor("_BaseColor", m_ColorSphere);
MeshRenderer.SetPropertyBlock(m_Block);
}
public void StartSequence()
{
StartCoroutine(FadeOut());
}
IEnumerator FadeOut()
{
float alpha = m_ColorSphere.a;
const float length = 4.0f;
for (float t = 0.0f; t < length; t += Time.deltaTime)
{
Color newColor = new Color(1, 1, 1, Mathf.Lerp(alpha, 1, t / length));
MeshRenderer.GetPropertyBlock(m_Block);
m_Block.SetColor("_BaseColor", newColor);
MeshRenderer.SetPropertyBlock(m_Block);
yield return null;
}
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
Hourglass
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
public class Hourglass : MonoBehaviour
{
[FormerlySerializedAs("hourglassTop")]
public GameObject HourglassTop;
[FormerlySerializedAs("hourglassBottom")]
public GameObject HourglassBottom;
[FormerlySerializedAs("timerFill")]
public float TimerFill = 1.0f;
[FormerlySerializedAs("particleSand")]
public ParticleSystem ParticleSand;
Material m_TopMat;
Material m_BotMat;
// Start is called before the first frame update
void Start()
{
m_TopMat = HourglassTop.GetComponent<MeshRenderer>().material;
m_BotMat = HourglassBottom.GetComponent<MeshRenderer>().material;
}
// Update is called once per frame
void Update()
{
if (Vector3.Dot(transform.up, Vector3.down) > 0.8 && TimerFill > 0)
{
TimerFill -= 0.1f * Time.deltaTime;
ParticleSand.Play();
}
else if (Vector3.Dot(transform.up, Vector3.down) < -0.8 && TimerFill < 1)
{
TimerFill += 0.1f * Time.deltaTime;
ParticleSand.Play();
} else
{
ParticleSand.Stop();
}
m_TopMat.SetFloat("TimerFill", TimerFill);
m_BotMat.SetFloat("TimerFill", TimerFill);
}
}
MagicBallProjectile
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MagicBallProjectile : ProjectileBase
{
Rigidbody m_Rigidbody;
float m_LaunchTime = 0;
bool m_Launched = false;
ObjectLauncher m_Launcher;
void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
m_Rigidbody.useGravity = false;
}
void Update()
{
if (m_Launched)
{
m_LaunchTime += Time.deltaTime;
if (m_LaunchTime > 10.0f)
{
//trigger a collision if we reached 10s without one, to force recycle the projectile, it's now too far
OnCollisionEnter(null);
}
}
}
public override void Launched(Vector3 direction, ObjectLauncher launcher)
{
//as they are pooled, they could have been already used and have previous velocity
m_Rigidbody.velocity = Vector3.zero;
m_Rigidbody.AddForce(direction * 200.0f);
m_Launcher = launcher;
m_Launched = true;
m_LaunchTime = 0.0f;
}
void OnCollisionEnter(Collision other)
{
gameObject.SetActive(false);
m_Launcher.ReturnProjectile(this);
m_Launched = false;
}
}
MagicTractorBeam
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
/// <summary>
/// Utility script that can pull toward the hand/controller the currently selected object through a Raycast.
/// </summary>
public class MagicTractorBeam : MonoBehaviour
{
public LineRenderer BeamRenderer;
public float TargetDistance = 0.05f;
public Vector3 LocalRayAxis = new Vector3(1,0,0);
public bool IsEnabled => BeamRenderer.gameObject.activeSelf;
public bool IsTracting => m_TractingObject;
XRDirectInteractor m_DirectInteractor;
Rigidbody m_HighlightedRigidbody;
SelectionOutline m_CurrentSelectionOutline = null;
bool m_TractingObject;
RaycastHit[] m_HitCache = new RaycastHit[16];
void Start()
{
m_DirectInteractor = GetComponentInChildren<XRDirectInteractor>();
}
void Update()
{
if(m_CurrentSelectionOutline != null)
m_CurrentSelectionOutline.RemoveHighlight();
if(m_TractingObject)
Tracting();
else if (IsEnabled)
{
Vector3 worldAxis = m_DirectInteractor.transform.TransformDirection(LocalRayAxis);
m_HighlightedRigidbody = null;
int mask = ~0;
mask &= ~(1 << LayerMask.NameToLayer("Hands"));
int count = Physics.SphereCastNonAlloc(m_DirectInteractor.transform.position, 0.2f, worldAxis, m_HitCache, 15.0f, mask, QueryTriggerInteraction.Ignore);
if (count != 0)
{
float closestDistance = float.MaxValue;
float closestDistanceGrabbable = float.MaxValue;
XRGrabInteractable closestGrababble = null;
for (int i = 0; i < count; ++i)
{
if (closestDistance > m_HitCache[i].distance)
closestDistance = m_HitCache[i].distance;
if (m_HitCache[i].rigidbody == null)
continue;
var grabbable = m_HitCache[i].rigidbody.GetComponentInChildren<XRGrabInteractable>();
if (grabbable != null && m_HitCache[i].distance < closestDistanceGrabbable)
{
closestDistanceGrabbable = m_HitCache[i].distance;
closestGrababble = grabbable;
}
}
BeamRenderer.SetPosition(1, LocalRayAxis * closestDistance);
if (closestGrababble != null)
{
var filter = closestGrababble.GetComponentInChildren<MeshFilter>();
if (filter != null)
{
m_HighlightedRigidbody = closestGrababble.GetComponent<Rigidbody>();
var outline = m_HighlightedRigidbody.GetComponentInChildren<SelectionOutline>();
if (outline != null)
{
m_CurrentSelectionOutline = outline;
m_CurrentSelectionOutline.Highlight();
}
}
}
}
}
}
void Tracting()
{
Vector3 target = m_DirectInteractor.transform.position - m_DirectInteractor.transform.right * TargetDistance;
Vector3 toTarget = target - m_HighlightedRigidbody.transform.position;
if(toTarget.sqrMagnitude > 1.0f)
toTarget.Normalize();
m_HighlightedRigidbody.velocity = Vector3.zero;
m_HighlightedRigidbody.AddForce(toTarget * 2.0f, ForceMode.VelocityChange);
}
public void StartTracting()
{
if (m_HighlightedRigidbody != null && m_DirectInteractor)
{
if (m_HighlightedRigidbody.TryGetComponent(out StationaryOnPull stationary))
{
m_HighlightedRigidbody.isKinematic = false;
}
m_TractingObject = true;
}
}
public void StopTracting()
{
if (m_HighlightedRigidbody != null)
{
if (!m_HighlightedRigidbody.isKinematic && (m_HighlightedRigidbody.TryGetComponent(out StationaryOnPull stationary)))
{
m_HighlightedRigidbody.isKinematic = true;
}
m_TractingObject = false;
m_HighlightedRigidbody.velocity = Vector3.zero;
m_HighlightedRigidbody = null;
}
}
public void EnableBeam()
{
//can't enable beam if we have an interactor currently holding something
if(m_DirectInteractor != null && m_DirectInteractor.isSelectActive)
return;
BeamRenderer.gameObject.SetActive(true);
}
public void DisableBeam()
{
BeamRenderer.gameObject.SetActive(false);
}
}
MasterController
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SpatialTracking;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
/// <summary>
/// Master script that will handle reading some input on the controller and trigger special events like Teleport or
/// activating the MagicTractorBeam
/// </summary>
public class MasterController : MonoBehaviour
{
static MasterController s_Instance = null;
public static MasterController Instance => s_Instance;
public XRRig Rig => m_Rig;
[Header("Setup")]
public bool DisableSetupForDebug = false;
public Transform StartingPosition;
public GameObject TeleporterParent;
[Header("Reference")]
public XRRayInteractor RightTeleportInteractor;
public XRRayInteractor LeftTeleportInteractor;
public XRDirectInteractor RightDirectInteractor;
public XRDirectInteractor LeftDirectInteractor;
public MagicTractorBeam RightTractorBeam;
public MagicTractorBeam LeftTractorBeam;
XRRig m_Rig;
InputDevice m_LeftInputDevice;
InputDevice m_RightInputDevice;
XRInteractorLineVisual m_RightLineVisual;
XRInteractorLineVisual m_LeftLineVisual;
HandPrefab m_RightHandPrefab;
HandPrefab m_LeftHandPrefab;
XRReleaseController m_RightController;
XRReleaseController m_LeftController;
bool m_PreviousRightClicked = false;
bool m_PreviousLeftClicked = false;
bool m_LastFrameRightEnable = false;
bool m_LastFrameLeftEnable = false;
LayerMask m_OriginalRightMask;
LayerMask m_OriginalLeftMask;
List<XRBaseInteractable> m_InteractableCache = new List<XRBaseInteractable>(16);
void Awake()
{
s_Instance = this;
m_Rig = GetComponent<XRRig>();
}
void OnEnable()
{
InputDevices.deviceConnected += RegisterDevices;
}
void OnDisable()
{
InputDevices.deviceConnected -= RegisterDevices;
}
void Start()
{
m_RightLineVisual = RightTeleportInteractor.GetComponent<XRInteractorLineVisual>();
m_RightLineVisual.enabled = false;
m_LeftLineVisual = LeftTeleportInteractor.GetComponent<XRInteractorLineVisual>();
m_LeftLineVisual.enabled = false;
m_RightController = RightTeleportInteractor.GetComponent<XRReleaseController>();
m_LeftController = LeftTeleportInteractor.GetComponent<XRReleaseController>();
m_OriginalRightMask = RightTeleportInteractor.interactionLayerMask;
m_OriginalLeftMask = LeftTeleportInteractor.interactionLayerMask;
if (!DisableSetupForDebug)
{
transform.position = StartingPosition.position;
transform.rotation = StartingPosition.rotation;
if(TeleporterParent != null)
TeleporterParent.SetActive(false);
}
InputDeviceCharacteristics leftTrackedControllerFilter = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Left;
List<InputDevice> foundControllers = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(leftTrackedControllerFilter, foundControllers);
if (foundControllers.Count > 0)
m_LeftInputDevice = foundControllers[0];
InputDeviceCharacteristics rightTrackedControllerFilter = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Right;
InputDevices.GetDevicesWithCharacteristics(rightTrackedControllerFilter, foundControllers);
if (foundControllers.Count > 0)
m_RightInputDevice = foundControllers[0];
//if (m_Rig.TrackingOriginMode != TrackingOriginModeFlags.Floor)
// m_Rig.cameraYOffset = 1.8f;
}
void RegisterDevices(InputDevice connectedDevice)
{
if (connectedDevice.isValid)
{
if ((connectedDevice.characteristics & InputDeviceCharacteristics.HeldInHand) == InputDeviceCharacteristics.HeldInHand)
{
if ((connectedDevice.characteristics & InputDeviceCharacteristics.Left) == InputDeviceCharacteristics.Left)
{
m_LeftInputDevice = connectedDevice;
}
else if ((connectedDevice.characteristics & InputDeviceCharacteristics.Right) == InputDeviceCharacteristics.Right)
{
m_RightInputDevice = connectedDevice;
}
}
}
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Escape))
Application.Quit();
RightTeleportUpdate();
LeftTeleportUpdate();
}
void RightTeleportUpdate()
{
Vector2 axisInput;
m_RightInputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out axisInput);
m_RightLineVisual.enabled = axisInput.y > 0.5f;
// RightTeleportInteractor.InteractionLayerMask = m_LastFrameRightEnable ? m_OriginalRightMask : new LayerMask();
if (axisInput.y <= 0.5f && m_PreviousRightClicked)
{
m_RightController.Select();
}
if (axisInput.y <= -0.5f)
{
if(!RightTractorBeam.IsTracting)
RightTractorBeam.StartTracting();
}
else if(RightTractorBeam.IsTracting)
{
RightTractorBeam.StopTracting();
}
//if the right animator is null, we try to get it. It's not the best performance wise but no other way as setup
//of the model by the Interaction Toolkit is done on the first update.
if (m_RightHandPrefab == null)
{
m_RightHandPrefab = RightDirectInteractor.GetComponentInChildren<HandPrefab>();
}
m_PreviousRightClicked = axisInput.y > 0.5f;
if (m_RightHandPrefab != null)
{
m_RightHandPrefab.Animator.SetBool("Pointing", m_PreviousRightClicked);
}
m_LastFrameRightEnable = m_RightLineVisual.enabled;
}
void LeftTeleportUpdate()
{
Vector2 axisInput;
m_LeftInputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out axisInput);
m_LeftLineVisual.enabled = axisInput.y > 0.5f;
// LeftTeleportInteractor.InteractionLayerMask = m_LastFrameLeftEnable ? m_OriginalLeftMask : new LayerMask();
if (axisInput.y <= 0.5f && m_PreviousLeftClicked)
{
m_LeftController.Select();
}
if (axisInput.y <= -0.5f)
{
if(!LeftTractorBeam.IsTracting)
LeftTractorBeam.StartTracting();
}
else if(LeftTractorBeam.IsTracting)
{
LeftTractorBeam.StopTracting();
}
//if the left animator is null, we try to get it. It's not the best performance wise but no other way as setup
//of the model by the Interaction Toolkit is done on the first update.
if (m_LeftHandPrefab == null)
{
m_LeftHandPrefab = LeftDirectInteractor.GetComponentInChildren<HandPrefab>();
}
m_PreviousLeftClicked = axisInput.y > 0.5f;
if (m_LeftHandPrefab != null)
m_LeftHandPrefab.Animator.SetBool("Pointing", m_PreviousLeftClicked);
m_LastFrameLeftEnable = m_LeftLineVisual.enabled;
}
}
ObjectLauncher
using System.Collections.Generic;
using UnityEngine;
public class ObjectLauncher : MonoBehaviour
{
public Transform SpawnPoint;
public ProjectileBase ObjectToSpawn;
public bool IsAutoSpawn = false;
public float LaunchRate = 0.5f;
public AudioClip LaunchingClip;
float m_LastLaunch = 0.0f;
Queue<ProjectileBase> m_ProjectilesPool = new Queue<ProjectileBase>();
void Awake()
{
enabled = false;
for (int i = 0; i < 32; ++i)
{
var newObj = Instantiate(ObjectToSpawn, SpawnPoint.position, SpawnPoint.rotation);
newObj.gameObject.SetActive(false);
m_ProjectilesPool.Enqueue(newObj);
}
}
public void Activated()
{
//if this is auto spawn regularly, we enable the script so the update is called.
if (IsAutoSpawn)
{
enabled = true;
m_LastLaunch = LaunchRate;
}
Launch();
}
public void Deactivated()
{
enabled = false;
}
void Update()
{
if (m_LastLaunch > 0.0f)
{
m_LastLaunch -= Time.deltaTime;
if (m_LastLaunch <= 0.0f)
{
Launch();
m_LastLaunch = LaunchRate;
}
}
}
void Launch()
{
var p = m_ProjectilesPool.Dequeue();
p.gameObject.SetActive(true);
p.transform.position = SpawnPoint.position;
p.Launched(SpawnPoint.transform.forward, this);
SFXPlayer.Instance.PlaySFX(LaunchingClip, SpawnPoint.position, new SFXPlayer.PlayParameters()
{
Pitch = Random.Range(0.9f, 1.2f),
Volume = 1.0f,
SourceID = -999
});
}
public void ReturnProjectile(ProjectileBase proj)
{
m_ProjectilesPool.Enqueue(proj);
}
}
public abstract class ProjectileBase : MonoBehaviour
{
public abstract void Launched(Vector3 direction, ObjectLauncher launcher);
}
ObjectSpawner
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VFX;
/// <summary>
/// Will spawn the given prefab, either when the Spawn function is called or if a MinInstance is setup, until that
/// amount exist in the scene. If Spawn is called when there is already MaxInstance number of object in the scene,
/// it will destroy the oldest one to make room for the newest.
/// </summary>
public class ObjectSpawner : MonoBehaviour
{
public GameObject Prefab;
public VisualEffect SpawnEffect;
public Transform SpawnPoint;
public int MaxInstances = 2;
public int MinInstance = 0;
List<GameObject> m_Instances = new List<GameObject>();
private void Start()
{
Prefab = null;
}
public void Spawn()
{
var newInst = Instantiate(Prefab, SpawnPoint.position, SpawnPoint.rotation);
if (m_Instances.Count >= MaxInstances)
{
Destroy(m_Instances[0]);
m_Instances.RemoveAt(0);
}
SpawnEffect.SendEvent("SingleBurst");
m_Instances.Add(newInst);
}
void Update()
{
for (int i = 0; i < m_Instances.Count; ++i)
{
//if the object was destroyed, remove from the list
if (m_Instances[i] == null)
{
m_Instances.RemoveAt(i);
i--;
}
}
if (Prefab != null)
{
while (m_Instances.Count < MinInstance)
{
Spawn();
}
}
}
}
Potion
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.VFX;
using Random = UnityEngine.Random;
public class Potion : MonoBehaviour{
static int NextFreeUniqueId = 3000;
public string PotionType = "Default";
public GameObject plugObj;
public ParticleSystem particleSystemLiquid;
public ParticleSystem particleSystemSplash;
public float fillAmount = 0.8f;
public GameObject popVFX;
[FormerlySerializedAs("meshRenderer")]
public MeshRenderer MeshRenderer;
[FormerlySerializedAs("smashedObject")]
public GameObject SmashedObject;
[Header("Audio")]
public AudioClip PouringClip;
public AudioClip[] PoppingPlugAudioClip;
public AudioClip[] BreakingAudioClips;
bool m_PlugIn = true;
Rigidbody m_PlugRb;
MaterialPropertyBlock m_MaterialPropertyBlock;
Rigidbody m_RbPotion;
int m_UniqueId;
AudioSource m_AudioSource;
bool m_Breakable;
float m_StartingFillAmount;
void OnEnable()
{
particleSystemLiquid.Stop();
if(particleSystemSplash)
particleSystemSplash.Stop();
m_MaterialPropertyBlock = new MaterialPropertyBlock();
m_MaterialPropertyBlock.SetFloat("LiquidFill", fillAmount);
MeshRenderer.SetPropertyBlock(m_MaterialPropertyBlock);
m_PlugRb = plugObj.GetComponent<Rigidbody>();
popVFX.SetActive(false);
m_RbPotion = GetComponent<Rigidbody>();
m_StartingFillAmount = fillAmount;
m_Breakable = true;
}
void Start()
{
m_AudioSource = SFXPlayer.Instance.GetNewSource();
m_AudioSource.gameObject.transform.SetParent(particleSystemLiquid.transform, false);
m_AudioSource.gameObject.SetActive(true);
m_AudioSource.clip = PouringClip;
m_AudioSource.maxDistance = 2.0f;
m_AudioSource.minDistance = 0.2f;
m_AudioSource.loop = true;
m_UniqueId = NextFreeUniqueId++;
}
void OnDestroy()
{
Destroy(m_AudioSource.gameObject);
}
// Update is called once per frame
void Update()
{
if (Vector3.Dot(transform.up, Vector3.down) > 0 && fillAmount > 0 && m_PlugIn == false)
{
if (particleSystemLiquid.isStopped)
{
particleSystemLiquid.Play();
m_AudioSource.Play();
}
fillAmount -= 0.1f * Time.deltaTime;
float fillRatio = fillAmount / m_StartingFillAmount;
m_AudioSource.pitch = Mathf.Lerp(1.0f, 1.4f, 1.0f - fillRatio);
RaycastHit hit;
if (Physics.Raycast(particleSystemLiquid.transform.position, Vector3.down, out hit, 50.0f, ~0, QueryTriggerInteraction.Collide))
{
PotionReceiver receiver = hit.collider.GetComponent<PotionReceiver>();
if (receiver != null)
{
receiver.ReceivePotion(PotionType);
}
}
}
else
{
particleSystemLiquid.Stop();
m_AudioSource.Stop();
}
MeshRenderer.GetPropertyBlock(m_MaterialPropertyBlock);
m_MaterialPropertyBlock.SetFloat("LiquidFill", fillAmount);
MeshRenderer.SetPropertyBlock(m_MaterialPropertyBlock);
}
public void ToggleBreakable(bool breakable)
{
m_Breakable = breakable;
}
public void PlugOff()
{
if (m_PlugIn)
{
m_PlugIn = false;
m_PlugRb.transform.SetParent(null);
m_PlugRb.isKinematic = false;
m_PlugRb.AddRelativeForce(new Vector3(0, 0, 120));
popVFX.SetActive(true);
m_PlugIn = false;
plugObj.transform.parent = null;
SFXPlayer.Instance.PlaySFX(PoppingPlugAudioClip[Random.Range(0, PoppingPlugAudioClip.Length)], m_PlugRb.transform.position, new SFXPlayer.PlayParameters()
{
Pitch = Random.Range(0.9f, 1.1f),
Volume = 1.0f,
SourceID = -99
});
}
}
private void OnCollisionEnter(Collision collision)
{
if (m_Breakable && m_RbPotion.velocity.magnitude > 1.35)
{
if (m_PlugIn)
{
m_PlugRb.isKinematic = false;
plugObj.transform.parent = null;
Collider c;
if (plugObj.TryGetComponent(out c))
c.enabled = true;
Destroy(plugObj, 4.0f);
}
foreach (Transform child in transform)
{
child.gameObject.SetActive(false);
}
if (particleSystemSplash != null)
{
particleSystemSplash.gameObject.SetActive(true);
if (fillAmount > 0)
{
particleSystemSplash.Play();
}
}
SmashedObject.SetActive(true);
SFXPlayer.Instance.PlaySFX(BreakingAudioClips[Random.Range(0, BreakingAudioClips.Length)], transform.position, new SFXPlayer.PlayParameters()
{
Pitch = Random.Range(0.8f, 1.2f),
SourceID = m_UniqueId,
Volume = 1.0f
});
Rigidbody[] rbs = SmashedObject.GetComponentsInChildren<Rigidbody>();
foreach (Rigidbody rb in rbs)
{
rb.AddExplosionForce(100.0f, SmashedObject.transform.position, 2.0f, 15.0F);
}
Destroy(SmashedObject, 4.0f);
Destroy(this);
}
}
}
Radio
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Radio : MonoBehaviour
{
public AudioSource MusicSource;
public AudioSource NoiseSource;
public AudioClip[] MusicClips;
float m_TuningRatio = 0.0f;
float m_VolumeRatio = 0.0f;
void Start()
{
Tune();
}
public void VolumeChanged(DialInteractable dial)
{
float ratio = dial.CurrentAngle / dial.RotationAngleMaximum;
m_VolumeRatio = ratio;
Tune();
}
public void TuningChanged(DialInteractable dial)
{
//off
if (dial.CurrentAngle < 0.01f)
{
MusicSource.Stop();
NoiseSource.Stop();
}
else if(!MusicSource.isPlaying)
{
MusicSource.Play();
NoiseSource.Play();
}
if(MusicClips.Length == 0)
return;
float ratio = dial.CurrentAngle / dial.RotationAngleMaximum;
float stepSize = dial.RotationAngleMaximum / MusicClips.Length;
float stepRatio = dial.CurrentAngle / stepSize;
int closest = Mathf.RoundToInt(stepRatio);
float dist = Mathf.Abs(closest - stepRatio) / 0.5f;
if (closest == 0)
dist = 1.0f;
else
{
AudioClip c = MusicClips[closest - 1];
if (c != MusicSource.clip)
{
int sample = MusicSource.timeSamples;
MusicSource.clip = c;
MusicSource.timeSamples = sample;
}
}
m_TuningRatio = 1.0f - dist;
Tune();
}
void Tune()
{
MusicSource.volume = m_TuningRatio * m_VolumeRatio;
NoiseSource.volume = (1.0f - m_TuningRatio) * m_VolumeRatio;
}
}
RespawnableObject
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// This will save the starting position/rotation of an object, and will move it back there when Respawn is called.
/// Useful for object that should never get destroyed when thrown in fire or the cauldron, like the ingredients.
/// </summary>
public class RespawnableObject : MonoBehaviour
{
Vector3 m_StartingPosition;
Quaternion m_StartingRotation;
Rigidbody m_Rigidbody;
void Start()
{
m_Rigidbody = GetComponent<Rigidbody>();
if (m_Rigidbody == null)
{
m_StartingPosition = transform.position;
m_StartingRotation = transform.rotation;
}
else
{
m_StartingPosition = m_Rigidbody.position;
m_StartingRotation = m_Rigidbody.rotation;
}
}
public void Respawn()
{
if (m_Rigidbody == null)
{
transform.position = m_StartingPosition;
transform.rotation = m_StartingRotation;
}
else
{
m_Rigidbody.velocity = Vector3.zero;
m_Rigidbody.angularVelocity = Vector3.zero;
m_Rigidbody.position = m_StartingPosition;
m_Rigidbody.rotation = m_StartingRotation;
}
}
}
WatchScript
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
public class WatchScript : MonoBehaviour
{
//by making a script subclass that, the wrist will pick them up at start time and will notify them it's been loaded
//this will allow them to add button to the UI that are scene specific.
public abstract class IUIHook : MonoBehaviour
{
public abstract void GetHook(WatchScript watch);
}
public float LoadingTime = 2.0f;
public Slider LoadingSlider;
[Header("UI")]
public Canvas RootCanvas;
public Transform ButtonParentTransform;
public Button ButtonPrefab;
public Toggle TogglePrefab;
[Header("Event")]
public UnityEvent OnLoaded;
public UnityEvent OnUnloaded;
public GameObject UILineRenderer;
bool m_Loading = false;
float m_LoadingTimer;
void Start()
{
LoadingSlider.gameObject.SetActive(false);
var hooks = FindObjectsOfType<IUIHook>();
foreach (var h in hooks)
{
h.GetHook(this);
}
RootCanvas.worldCamera = Camera.main;
}
// Update is called once per frame
void Update()
{
if (m_Loading)
{
m_LoadingTimer += Time.deltaTime;
LoadingSlider.value = Mathf.Clamp01(m_LoadingTimer / LoadingTime);
if (m_LoadingTimer >= LoadingTime)
{
OnLoaded.Invoke();
UILineRenderer.SetActive(true);
LoadingSlider.gameObject.SetActive(false);
m_Loading = false;
}
}
}
public void LookedAt()
{
m_Loading = true;
m_LoadingTimer = 0.0f;
LoadingSlider.value = 0.0f;
LoadingSlider.gameObject.SetActive(true);
}
public void LookedAway()
{
m_Loading = false;
OnUnloaded.Invoke();
LoadingSlider.gameObject.SetActive(false);
UILineRenderer.SetActive(false);
}
public void AddButton(string name, UnityAction clickedEvent)
{
var newButton = Instantiate(ButtonPrefab, ButtonParentTransform);
var text = newButton.GetComponentInChildren<Text>();
RecursiveLayerChange(newButton.transform, ButtonParentTransform.gameObject.layer);
text.text = name;
newButton.onClick.AddListener(clickedEvent);
}
public void AddToggle(string name, UnityAction<bool> checkedEvent)
{
var newToggle = Instantiate(TogglePrefab, ButtonParentTransform);
var text = newToggle.GetComponentInChildren<Text>();
RecursiveLayerChange(newToggle.transform, ButtonParentTransform.gameObject.layer);
text.text = name;
newToggle.onValueChanged.AddListener(checkedEvent);
}
void RecursiveLayerChange(Transform root, int layer)
{
foreach (Transform t in root)
{
RecursiveLayerChange(t, layer);
}
root.gameObject.layer = layer;
}
}
WitchHouseUIHook
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// This will be picked up automatically by the wrist watch when it get spawn in the scene by the Interaction toolkit
/// and setup the buttons and the linked events on the canvas
/// </summary>
public class WitchHouseUIHook : WatchScript.IUIHook
{
public GameObject LeftUILineRenderer;
public GameObject RightUILineRenderer;
public override void GetHook(WatchScript watch)
{
watch.AddButton("Reset", () => { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); });
watch.AddButton("Unlock Teleporters", () => {MasterController.Instance.TeleporterParent.SetActive(true);});
watch.AddToggle("Closed Caption", (state) => { CCManager.Instance.gameObject.SetActive(state); });
LeftUILineRenderer.SetActive(false);
RightUILineRenderer.SetActive(false);
watch.UILineRenderer = LeftUILineRenderer;
}
}
WoodenLogBehaviour
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
public class WoodenLogBehaviour : MonoBehaviour
{
[FormerlySerializedAs("meshRenderer")]
public MeshRenderer MeshRenderer;
MaterialPropertyBlock m_Block;
float m_BurnAmount = 0.0f;
bool m_Burn = false;
// Start is called before the first frame update
void Start()
{
m_Block = new MaterialPropertyBlock();
m_Block.SetFloat("BurnAmount", m_BurnAmount);
MeshRenderer.SetPropertyBlock(m_Block);
}
// Update is called once per frame
void Update()
{
if (m_Burn && m_BurnAmount < 1.0f)
{
m_BurnAmount += 0.2f * Time.deltaTime;
}
MeshRenderer.GetPropertyBlock(m_Block);
m_Block.SetFloat("BurnAmount", m_BurnAmount);
MeshRenderer.SetPropertyBlock(m_Block);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Ignite")
{
m_Burn = true;
}
}
}