using System.Collections; using System.Collections.Generic; using UnityEngine; public class BallPathTest : MonoBehaviour { public AudioSource audioToSync; public List allHits = new List(); public LineRenderer line; public Transform mover; public float horizontalSpeed = 1f; public float gravity = -9f; public float gravityWhenUpwardMult = 1f; public float maxFallSpeed = 10; public AnimationCurve bouncingEfficiencyGraph = new AnimationCurve(); // public float bouncingEfficiency =0.4f; public float changeDirCloseThresh = 0.1f; public Vector2 velocity; public bool simulate = false; public GameObject bouncerPrefab; public Transform bouncerParent; void OnValidate() { if (simulate) { Simulate(); simulate = false; } } float timer = 0; public List hitsCache = new List(); List pointsHistory = new List(); List hitPointsHistory = new List(); void Start() { Reset(Time.time); } void Reset(float time){ velocity = new Vector2(horizontalSpeed, 0); mover.transform.position = Vector2.zero; hitsCache = new List(); hitsCache.AddRange(allHits); pointsHistory= new List(); hitPointsHistory= new List(); prevTime=time; timer =0; } // Update is called once per frame void Update() { ManualUpdate(Time.time); } float prevTime; bool ManualUpdate(float time){ float dt = time - prevTime; prevTime = time; timer += dt; audioToSync.time = time; if(hitsCache.Count <= 1){return true;} velocity += new Vector2(0, gravity*dt * (velocity.y > 0 ? gravityWhenUpwardMult : 1)); if (timer > hitsCache[0]) { float timeToNextHit = hitsCache[1]-hitsCache[0];//(hitsCache.Count >1)?1:0 Debug.Log($"hit: {timeToNextHit}, velocity : {velocity}"); BounceData bounceData = new BounceData(); bounceData.point = mover.position; bounceData.prevVelocity = velocity; //Bounce hitsCache.RemoveAt(0); bool changeDir= timeToNextHit < changeDirCloseThresh; float bouncingEfficiency = bouncingEfficiencyGraph.Evaluate(Mathf.Abs(velocity.y)/maxFallSpeed); velocity = new Vector2(velocity.x * (changeDir ? -1 : 1), velocity.y * -bouncingEfficiency * (changeDir ? 0:1)); bounceData.newVelocity = velocity; hitPointsHistory.Add(bounceData); } velocity = new Vector2(velocity.x, velocity.y < -maxFallSpeed ? -maxFallSpeed : velocity.y); mover.transform.position += (Vector3)velocity * dt; pointsHistory.Add(mover.transform.position); return false; } void Simulate(){ float fps = 60; float totalLength = allHits[allHits.Count-1]; float curFrame =0; Reset(0); //Purge existing bouncing plats bouncerParent.PurgeChildrenEdit(); int totalFrames = Mathf.CeilToInt(totalLength * fps); while(curFrame < totalFrames){ if(ManualUpdate(curFrame/fps)){ break; } curFrame++; } line.positionCount = pointsHistory.Count; line.SetPositions(pointsHistory.ToArray()); foreach(BounceData data in hitPointsHistory){ GameObject newPlat = Instantiate(bouncerPrefab, bouncerParent); newPlat.transform.position = data.point; newPlat.transform.rotation = Quaternion.AngleAxis(CalculateWallAngle(data.prevVelocity,data.newVelocity), Vector3.forward); } } public static float CalculateWallAngle(Vector2 previousVelocity, Vector2 newVelocity) { // Normalize velocities to ignore the speed and just focus on direction Vector2 previousDir = previousVelocity.normalized; Vector2 newDir = newVelocity.normalized; // The reflection vector is the difference between the two normalized velocities Vector2 reflectionVector = newDir - previousDir; // Calculate the wall normal using reflection law (midpoint of incoming and outgoing angles) Vector2 wallNormal = Vector2.Perpendicular(reflectionVector).normalized; // Calculate the angle of the wall from the wall's normal float wallAngle = Mathf.Atan2(wallNormal.y, wallNormal.x) * Mathf.Rad2Deg; // Make sure the angle is between 0 and 360 degrees if (wallAngle < 0) wallAngle += 360; return wallAngle; } } [System.Serializable] public struct BounceData{ public Vector3 point; public Vector2 prevVelocity; public Vector2 newVelocity; }