Code:

using System.Collections; using System.Collections.Generic; using UnityEngine; using TheGuysYouDespise; public class HornBlock : BlockScript { //BlockSettings protected MKey activateKey; protected MSlider pitchSlider; protected MSlider volumeSlider; protected MToggle semitoneToggle; //other stuff protected AudioSource audioSource; protected bool hasSound = false; protected float HornsWithMyKeyCoefficient = 0f; protected bool flipped = false; protected Vector3 orgScale = Vector3.one; protected bool steamPowered = false; protected float steamPoweredTimer = 10f; protected float lastSoundTimer = 10f; //for the semitone toggle we'll be using we want to know a list of intervals we can use for that protected float[] musicIntervals = new float[] {1/1f, 16/15f, 9/8f, 6/5f, 5/4f, 4/3f, 64/45f, 3/2f, 8/5f, 5/3f, 16/9f, 15/8f, 2/1f}; protected bool waitAFrame = true; //BlockLoader Specific method: is called right after the script is made - usually, //this is done for all blocks of this type and is safe as it waits for stuff like //colliders, visuals, resources and so on public override void SafeAwake() { //Set the blocks original scale orgScale = transform.localScale; activateKey = AddKey("Sound The Horn", //Display text "play", //save name KeyCode.H); //Keyboard button pitchSlider = AddSlider("Pitch", //Display Text "pitch", //save name 1f, //default value 0.5f, //minimum value 2f); //maximum value volumeSlider = AddSlider("Volume", //Display Text "volume", //save name 1f, //default value 0.1f, //minimum value 1f); //maximum value semitoneToggle = AddToggle("Semitone Snap", //Display Text "semitones", //save name true); //default value //Call HandleSemiToneSnap() when the slider value changes pitchSlider.ValueChanged += HandleSemitoneSnap; } //BlockLoader Specific method: Is the safe 1 time called method for the prefab - that's the master gameobject, or template so to speek //if you need to make alterations to the block you couldn't do with the standard framework, do it here. public override void OnPrefabCreation() { } //BlockLoader Specific method: When the player presses spacebar or the simulate/play button in the upper left corner protected override void OnSimulateStart() { //if the sound file we loaded in the LoadExampleBlock if (resources.ContainsKey("warHorn.ogg")) { hasSound = true; //set the audio source we'll be using to the respective component, but if we don't find one, add one. audioSource = gameObject.GetComponent<AudioSource>() ?? gameObject.AddComponent<AudioSource>(); //set the clip the audio source will be playing to the one we loaded. audioSource.clip = resources["warHorn.ogg"].audioClip; } //This is mostly flair: //For all blocks in the machine we have during simulation foreach (Transform t in Machine.Active().SimulationMachine) { //get the ones that are a HornBlock if (t.GetComponent<HornBlock>()) { //and if they use the save keyboard button to play their sound if (t.GetComponent<HornBlock>().activateKey.KeyCode[0] == activateKey.KeyCode[0]) { //Add their volume to a coefficient we need for later HornsWithMyKeyCoefficient += t.GetComponent<HornBlock>().volumeSlider.Value; } } } } //BlockLoader Specific method: When the player is simulating instead of building protected override void OnSimulateUpdate() { //as long as our block isn't destroyed if (!HasBurnedOut() && !Destroyed()) { //when we press the key if (activateKey.IsPressed) { //Play the horn sound SoundTheHorn(); } //Or when steam powers the horn else if(steamPowered) { //We check all this stuff to stop spamming of powering the horn if (!audioSource.isPlaying || steamPoweredTimer > 0.1f || lastSoundTimer > 1f) { //Play the horn sound lastSoundTimer = 0f; SoundTheHorn(); } } lastSoundTimer += Time.deltaTime; steamPoweredTimer += Time.deltaTime; steamPowered = false; } } //Checking Steam powering protected virtual void OnParticleCollision(GameObject other) { //ignore if we are already steam powered if (!steamPowered) { //if collison is with steam if (other.name == "SteamParticle") //if it's entering from the top-ish if (Vector3.Angle(-other.transform.forward, transform.forward) < 55f) { steamPoweredTimer = 0f; steamPowered = true; } } } //HornBlock defined method: We have made this method to play the sound we want public void SoundTheHorn() { StopCoroutine(ShakeScale(transform.FindChild("Vis").gameObject, 0.025f, 0.25f / pitchSlider.Value)); StartCoroutine(ShakeScale(transform.FindChild("Vis").gameObject, 0.025f, 0.25f / pitchSlider.Value)); if (hasSound) { //Set a couple of frames of random delay (we don't use Time.deltaTime, because if we have low fps we don't want to wait longer for the sounds to play) audioSource.PlayDelayed(Random.Range(0f, 0.0165f * 3f)); //Set the pitch - the brightness - of the sound to be dependent on the timescale and the slider we have for it audioSource.pitch = Time.timeScale * pitchSlider.Value; //This is the flair coefficient, this basically helps the sounds to be dimmed when a lot of blocks play at the same time float Coefficient = 0.6f / (HornsWithMyKeyCoefficient < 4.5f ? 1f : HornsWithMyKeyCoefficient / 3f); //Set the volume - the loudness - of the sound to be dependent on the slider and coefficient audioSource.volume = volumeSlider.Value * Coefficient; } } //BlockLoader Specific method: When we are done simulating, usually you don't need to do anything here, //as the block in simulation mode is deleted, but if you have static variables or similar you might want to update it here. protected override void OnSimulateExit() { //reset this flair thing for good meassure HornsWithMyKeyCoefficient = 0f; } //Flip the block on F, this method needs to be called "Flip" public void Flip() { //play the flipping sound - BlockScript predefined PlayFlipSound(); //Do the flipping we need: FlipNoSound(); } //Setting the flip needed for copying, loading and such. public void SetFlipNoSound(bool flip) { //we don't want to do the flip if the flipping matches if (flipped == flip) return; //Do the flipping we need: FlipNoSound(); } //HornBlock defined method: We flip the block litterally public void FlipNoSound() { //reverse the flipping bool flipped = !flipped; //mirror the visuals of the block to be... well... mirrored foreach(var vis in Visuals) MirrorVisual(vis); //BlockScript defined method //mirror the colliders in a different way where we make sure to rotate the colliders to achieve the correct stuff foreach (var col in Colliders) MirrorCollider(col); //BlockScript defined method } //HornBlock defined method: //Animates a shake in the scale of a gameobject protected IEnumerator ShakeScale(GameObject go, float magnitude, float duration) { float elapsed = Time.deltaTime; duration += Time.deltaTime; Vector3 orgScale = go.transform.localScale; Vector3 fromScale = orgScale; Vector3 targetScale = orgScale; float previousCoef = 10f; while (elapsed < duration) { elapsed += Time.deltaTime; //this if statement deals with time scale, //for >100% time scale it's run every frame //but as the time scale is lower we don't want the shaking to appear much faster than fitting for the time //in that scenario we will sample the shaking every now and then //we then smoothly lerp between the samples if (Time.timeScale >= 0.9f || (elapsed % 0.01666f) / 0.01666f < previousCoef) { go.transform.localScale = targetScale; //dampen based on the tails of the animation float percentComplete = elapsed / duration; float damper = 1.0f - Mathf.Clamp(5.0f * percentComplete - 4.0f, 0.0f, 1.0f); // map value to [-1, 1] float x = Random.value * 2.0f - 1.0f; float y = Random.value * 2.0f - 1.0f; // dampen x *= magnitude * damper; y *= magnitude * damper; //Set the two points to lerp between fromScale = go.transform.localScale; targetScale = new Vector3(orgScale.x + x, orgScale.y + y, orgScale.z); } previousCoef = (elapsed % 0.01666f) / 0.01666f; go.transform.localScale = Vector3.Lerp(fromScale, targetScale, (elapsed % 0.01666f) / 0.01666f); yield return null; } go.transform.localScale = orgScale; yield break; } //HornBlock defined method: make sure that the pitch only can fit a musical interval when toggled protected virtual void HandleSemitoneSnap(float value) { //we have to wait a frame not to create a feedback loop where when we change the value to snap to a semi tone it will then call this function again if (waitAFrame) { waitAFrame = false; float lower, upper; if (semitoneToggle.IsActive) { //we go through all of the semitones for (int i = 0; i < musicIntervals.Length - 1; i++) { lower = musicIntervals[i]; upper = musicIntervals[i + 1]; //for 2 octaves, below and above the for (int octave = 1; octave <= 2; octave++) { //we are inversing the intervals to get the intervals below 1.0 and above lower = 1f / lower; upper = 1f / upper; //see whether the value is between the lower and upper (lower and upper can be inverse, due to we inversing the fraction) if ((value >= lower && value <= upper) || (value >= upper && value <= lower)) { //find the closer of the values if (Mathf.Abs(lower - value) < Mathf.Abs(upper - value)) { pitchSlider.Value = lower; goto ReturnAndUpdate; } else { pitchSlider.Value = upper; goto ReturnAndUpdate; } } } } //we were outside the range of the intervals //clamp the value to the absolute lower and upper lower = 1f / musicIntervals[musicIntervals.Length - 1]; upper = musicIntervals[musicIntervals.Length - 1]; pitchSlider.Value = Mathf.Clamp(value, lower, upper); ReturnAndUpdate: StopAllCoroutines(); StartCoroutine(UpdateMapper()); } } else waitAFrame = true; } //HornBlock defined method protected virtual IEnumerator UpdateMapper() { if (BlockMapper.CurrentInstance == null) yield break; while (Input.GetMouseButton(0)) yield return null; BlockMapper.CurrentInstance.Copy(); BlockMapper.CurrentInstance.Paste(); yield break; } //The following functions are for saving, loading, copying and pasting the values //Besiege Specific method public override void OnSave(BlockXDataHolder data) { SaveMapperValues(data); data.Write("flipped", flipped); } //Besiege Specific method public override void OnLoad(BlockXDataHolder data) { LoadMapperValues(data); // If the simulation just started we do not want to load // our flip state from the data holder. // The flip variable is automatically copied // over by Unity when the simulation starts. if (data.WasSimulationStarted) return; if (data.HasKey("flipped")) { SetFlipNoSound(data.ReadBool("flipped")); } } }