using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using EvoDbManager; public class FruitHarvesting : MonoBehaviour { [System.Serializable] public class FruitGroup { public List<GameObject> fruitList; private List<Vector3> initialFruitSizes = new List<Vector3>(); public List<Vector3> InitialFruitSizes { get { return initialFruitSizes; } set { initialFruitSizes = value; } } } //Amount of times we need to click on the tree before we can harvest the fruit public int HarvestShakesRequired = 3; public bool UseTestTime = false; //Test wait time in seconds public float TestWaitTime = 10; public ParticleSystem LeafParticleSystem; public Sprite FruitSprite; //Tracks if we are able to shake the tree //Is true by default. Set to false while the tree is already shaking private bool _canShake = true; //Tracks completed shakes during harvesting private int _harvestShakesCompleted = 0; //Tracks if we are able to harvest the tree //Is false by default, only true when the timer has reached 0 and the fruit can be harvested private bool _canHarvest = false; public string EnvironmentItemID; public List<FruitGroup> FruitGroups; private int currentFruitGroupIndex = 0; //List that stores initial y values of harvestable fruit //so we can reset the y value after the fruit falls private List<float> harvestableFruitInitialY; private SpriteRenderer spriteRenderer; MaterialPropertyBlock mpb; float outlineAlpha = 0; Sequence outlinePulseSequence; EnvironmentItem_Logic environmentItem_Logic; float timeLeftToGrow; List<float> percentagesComplete; bool updateOutlineShader = false; private EVODbEnvironmentItem dbEnvironmentItem; private float waitTime; //Set up everything and run GrowFruit coroutine so fruit starts growing right away private void Start() { dbEnvironmentItem = EVODbManager.Shared.environmentItems[EnvironmentItemID]; EnvironmentItemID = StringHelper.ReplaceWhitespace(EnvironmentItemID, ""); environmentItem_Logic = new EnvironmentItem_Logic(); if(UseTestTime && GameEnvironmentInfo.IsEditorOrDevelopmentBuild()) { waitTime = TestWaitTime; } else { waitTime = (float)dbEnvironmentItem.waitTime; } //Start logic, will use wait time later for tweening environmentItem_Logic.Start(dbEnvironmentItem.uniqueId, waitTime); spriteRenderer = this.GetComponent<SpriteRenderer>(); mpb = new MaterialPropertyBlock(); spriteRenderer.GetPropertyBlock(mpb); harvestableFruitInitialY = new List<float>(); percentagesComplete = new List<float>(); for (int i = 0; i < FruitGroups.Count; i++) { SetInitialSizes(FruitGroups[i]); if (i == FruitGroups.Count - 1) { //Set inital positions for only the last group of fruit (The harvesteable fruit), //as this is the only fruit that will fall and will need it's position reset. SetInitialFruitPosition(FruitGroups[i].fruitList); } } StartFruitGrowing(); } void SetInitialSizes(FruitGroup fruitGroup) { for (int i = 0; i < fruitGroup.fruitList.Count; i++) { fruitGroup.InitialFruitSizes.Add(fruitGroup.fruitList[i].transform.localScale); } } private void StartFruitGrowing() { double timeLeft = environmentItem_Logic.SetLastInteraction_TotalSeconds(); if (timeLeft < 0) { timeLeft = 0; } //First we calculate the percentage of time we have left to wait //(time remaining / total time to wait) * 100 float percentageLeft = (Convert.ToSingle(timeLeft / waitTime)) * 100; //We get the inverse by doing 100 - time remaining //so we have the percentage amount of time that has passed so far. float percentageCompleted = 100 - percentageLeft; //We calculate the percentage out of 100 of each fruit group //Normally we have 4 fruit groups so 100 / 4 = 25 float oneGroupPercent = 100 / FruitGroups.Count; //Loops through all of the object lists for (int i = 0; i < FruitGroups.Count; i++) { percentagesComplete.Add(percentageCompleted); //Get min and max percentage of each fruit group float min = oneGroupPercent * i; float max = min + oneGroupPercent; //If our percentage completed is within the min and max of this fruit group //It means this is the fruit group that is currently growing and therefore we set its //size based on the percentWithinRange if (percentageCompleted > min && percentageCompleted <= max) { //Here we get the percentage out of 100 that the percentage completed is within each fruit groups min/max range //((input - min) * 100) / (max - min) //Example: If our time completed is 15%, we calculate ((15 - 0) * 100) / (25 - 0) = 60% //We can then use this percentage to calculate the size the fruit in the group should be //with 0% being at a size of 0, and 100% being at its largest size float percentWithinRange = ((percentageCompleted - min) * 100) / (max - min); SetInitialFruitSizes(FruitGroups[i], percentWithinRange); currentFruitGroupIndex = i; } //All other fruits are not growing and should therefore not be showing, //so we set their sizes to 0 else { SetInitialFruitSizes(FruitGroups[i], 0); } } timeLeftToGrow = Convert.ToSingle(timeLeft); StartCoroutine(GrowFruit()); } //Save list of initial fruit sizes so we know what their end size should be when scaling them up private void SetInitialFruitSizes(FruitGroup fruitGroup, float scale) { for (int i = 0; i < fruitGroup.fruitList.Count; i++) { //If we pass in a scale of zero, set scale to zero with no special logic if (scale == 0) { fruitGroup.fruitList[i].transform.localScale = new Vector3(0, 0, 1); } //Otherwise, we will calculate the fruit's size based on the percentage we pass in else { //Here we calculate each fruits size based on percentWithinRange and the fruits maximum/starting size //Value = percentage * max / 100 //Example: If our fruit is 60% grown, then our fruit size would be 60 * .5f / 100 = .3f, //so .3 is 60% in a range of 0-.5f float currentScaleValueX = scale * fruitGroup.fruitList[i].transform.localScale.x / 100; float currentScaleValueY = scale * fruitGroup.fruitList[i].transform.localScale.y / 100; fruitGroup.fruitList[i].transform.localScale = new Vector3(currentScaleValueX, currentScaleValueY, 1); } } } //Save list of initial harvesteable fruit y positions so we can reset them later after they fall private void SetInitialFruitPosition(List<GameObject> objList) { for (int i = 0; i < objList.Count; i++) { harvestableFruitInitialY.Add(objList[i].transform.localPosition.y); } } //Used to loop through objects and call a function for each object. //We pass in a tween function to be run private void TweenFruitGroup(Func<GameObject, float, Tween> TweenFunction, FruitGroup fruitGroup, Sequence s, float duration) { for (int i = 0; i < fruitGroup.fruitList.Count; i++) { s.Join(TweenFunction.Invoke(fruitGroup.fruitList[i], duration)); } } //Override with object sizes private void TweenFruitGroup(Func<Vector2, GameObject, float, Tween> TweenFunction, FruitGroup fruitGroup, Sequence s, float duration) { for (int i = 0; i < fruitGroup.fruitList.Count; i++) { s.Join(TweenFunction.Invoke(fruitGroup.InitialFruitSizes[i], fruitGroup.fruitList[i], duration)); } } //Reset all fruits after you harvest them private void ResetFruit(FruitGroup fruitGroup) { bool resetY = false; //If this is the last fruit group (which is the harvesteable group), reset the y pos for that group if (FruitGroups.IndexOf(fruitGroup) == FruitGroups.Count - 1) { resetY = true; } for (int i = 0; i < fruitGroup.fruitList.Count; i++) { if (resetY) { //Reset y pos back to initial Vector3 pos = Vector3.zero; pos.x = fruitGroup.fruitList[i].transform.localPosition.x; pos.y = harvestableFruitInitialY[i]; pos.z = fruitGroup.fruitList[i].transform.localPosition.z; fruitGroup.fruitList[i].transform.localPosition = pos; } //Reset size to zero fruitGroup.fruitList[i].transform.localScale = Vector3.zero; //Reset sprite alpha to 1 SpriteRenderer sRenderer = fruitGroup.fruitList[i].GetComponent<SpriteRenderer>(); Color c = sRenderer.color; c.a = 1; sRenderer.color = c; } } //Coroutine where we "grow" the fruits by scaling them based on the environment item's wait time private IEnumerator GrowFruit() { //Create new Last fruit group list that will hold the previous list FruitGroup LastFruitGroup = null; //Loop through all object lists and call tween logic for each list //pass in our sequence so we can append/join tweens to our sequence for (int i = currentFruitGroupIndex; i < FruitGroups.Count; i++) { //Create a new sequence. We will use this sequence for all of the object scale tweens //so we control when the objects tween and what to do when they are all finished tweening. Sequence growFruitSequence = DOTween.Sequence(); currentFruitGroupIndex = i; if (LastFruitGroup != null) { TweenFruitGroup(TweenHelper.FadeOut, LastFruitGroup, growFruitSequence, 1.3f); } float timeCompleted = Convert.ToSingle(waitTime - timeLeftToGrow); float timeToCompleteOneGroup = Convert.ToSingle(waitTime) / FruitGroups.Count; //Get min and max time to grow for each fruit group float min = timeToCompleteOneGroup * i; float max = min + timeToCompleteOneGroup; TweenFruitGroup(TweenHelper.Scale, FruitGroups[i], growFruitSequence, max - timeCompleted); //Set last fruit group LastFruitGroup = FruitGroups[i]; //If next index is = to the count, we are currently on our last group if (i + 1 == FruitGroups.Count) { //After all of our tweens are finished scaling, //we set our _canHarvest bool to true which means we are now able to start clicking on the tree to harvest it growFruitSequence.OnComplete(() => { //Create the outline pulsing in and out effect //This will loop infinitely until the sequence is killed after the tree has been harvested CreateOutlineTween(); updateOutlineShader = true; _canHarvest = true; }); } //Wait for all objects in current group to finish tweening //Once finished we can continue the loop and move onto the next group list yield return growFruitSequence.WaitForCompletion(); } } void CreateOutlineTween() { //Create the outline pulsing in and out effect //This will loop infinitely until the sequence is killed after the tree has been harvested outlinePulseSequence = DOTween.Sequence(); outlinePulseSequence.SetAutoKill(false); outlinePulseSequence.Append(DOTween.To(() => outlineAlpha, x => outlineAlpha = x, 1, .6f).SetEase(Ease.InQuad)); outlinePulseSequence.AppendInterval(.12f); outlinePulseSequence.Append(DOTween.To(() => outlineAlpha, x => outlineAlpha = x, 0, .6f).SetEase(Ease.InQuad)); outlinePulseSequence.AppendInterval(.12f); outlinePulseSequence.OnComplete(() => outlinePulseSequence.Restart()); } //ShakePlant is called by the OnTouch event triggered by the DistanceTouchEventTrigger script //When we are allowed to tap on the item based on our distance from it, this function is called public void ShakePlant() { //The ShakePlant function is run from the OnTouch() event handler in the DistanceTouchEventTrigger script //Only run shake logic when the tweens aren't currently running if (_canShake) { //Can not shake the tree while fruits are tweening _canShake = false; if (_canHarvest) { InputManager.Instance.DisableRigidbodyMovement(); _ = QuestionSceneLoader.LoadQuestionScene(DoPostQuestionLogic, QuestionContext.Vegetation); } else { Sequence shakeSequence = DoShakeTweenSequence(); shakeSequence.OnComplete(() => { //If fruit is not falling to the ground and this is just a transition tween, //player can shake tree again after tweening is finished _canShake = true; }); } } } private Sequence DoShakeTweenSequence() { //Add tweens to tween sequence Sequence shakeSequence = TweenHelper.ShakeSequence(this.gameObject, .8f, new Vector2(.02f, .02f), 3); LeafParticleSystem.Play(); //Shake tree //Tween fruit shake for all the current group TweenFruitGroup(TweenHelper.ShakeRot, FruitGroups[currentFruitGroupIndex], shakeSequence, .8f); return shakeSequence; } void DoPostQuestionLogic(QuestionAnswerInfo callback) { DoShakeTweenSequence(); InputManager.Instance.EnableRigidbodyMovement(); //increment the amount of shakes we have completed so far _harvestShakesCompleted++; //Check for which TweenFall we should run CheckFruitFall(); } //Set Fruit fall logic for harvesting based on if this is our final shake or not void CheckFruitFall() { Sequence fruitFallSequence = DOTween.Sequence(); //if we are not on our final shake, //tween the fruits without the fall to ground effect if (_harvestShakesCompleted != HarvestShakesRequired) { //Tween fruit small fall for all the current group TweenFruitGroup(TweenHelper.SmallFall, FruitGroups[currentFruitGroupIndex], fruitFallSequence, .5f); fruitFallSequence.OnComplete(() => { _canShake = true; }); } //if we ARE on our final shake, //tween the fruits WITH the fall to ground effect else { //Tween fruit fall to ground for all the current group TweenFruitGroup(TweenHelper.GroundFall, FruitGroups[currentFruitGroupIndex], fruitFallSequence, .4f); //after fruit falls to the group, show rewards fruitFallSequence.OnComplete(() => { NavigationUIController.Instance.ShowVegetationRewardsUI(environmentItem_Logic, dbEnvironmentItem, FruitSprite); RestartTreeHarvesting(); }); } } //Restart logic to restart everything after you have finished harvesting void RestartTreeHarvesting() { //Tree has been harvested and apples have fallen to ground //reset _canHarvest bool and reset values so fruit can start growing again _canHarvest = false; _harvestShakesCompleted = 0; outlinePulseSequence.OnComplete(() => updateOutlineShader = false); outlinePulseSequence.SetAutoKill(true); //Fade out apples on ground Sequence fadeOutSequence = DOTween.Sequence(); TweenFruitGroup(TweenHelper.FadeOut, FruitGroups[currentFruitGroupIndex], fadeOutSequence, 1.3f); //After apples have faded out, set their positions back to their initial positions, //set all groups scale back to zero, and set all groups alpha back to 1. This gets them ready for growing again fadeOutSequence.OnComplete(() => { _canShake = true; //Loop through all groups for (int i = 0; i < FruitGroups.Count; i++) { //Reset y pos of current group (harvestable group) //Reset sizes and alpha of all items in all groups ResetFruit(FruitGroups[i]); } environmentItem_Logic.UpdateInteractionTime(); StartFruitGrowing(); }); } private void Update() { if (updateOutlineShader) { UpdateOutlineAlpha(); } } //Set alpha property of outline in our shader to the alpha we are tweening to create a phasing effect void UpdateOutlineAlpha() { mpb.SetFloat("_OutlineAlpha", outlineAlpha); spriteRenderer.SetPropertyBlock(mpb); } }
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter