This posts describes how to make a simple object bouncing, acceleration and glue platform behaviour for Unit 3D. In action, it looks like this:
The behaviour is realised by setting a simple execution behaviour (script) on the player (red) and a property definition script on each obstacle (green) make the entire physics-based ping-pong happen. There is no additional code for movement in this scene other than the forces applied each time the player hits the green obstacles.
Currently, the script supports active bouncing (flipper effect), a glue on contact with timer and a sideways acceleration, defining the velocity amplification factor individually for each obstacle. The script also supports (set in inspector) callbacks for all major events in order to adapt animation, sounds etc. Everything is based on physics and forces in order to make it “realistic” in the play flow.
Lastest version of source code: https://github.com/imifos/unity3d-workspace/pingpongtest
Assets/ObjectVelocityModifier2DController.cs – Added to the ball/player
using System.Collections; using UnityEngine; public class ObjectVelocityModifier2DController : MonoBehaviour { private Rigidbody myBody; private Vector3 velocityBeforeImpact; private Vector3 velocityBeforeGlue; private GameObject lastCollidedObject=null; void Start() { myBody = GetComponent<Rigidbody>(); } void FixedUpdate() { // If we need the original velocity, we need to keep the velocity because // the physics engine re-sets the velocity on impact according to physics, // so in "OnCollisionEnter()", we don't have access to this 'pure' velocity anymore. velocityBeforeImpact = myBody.velocity; } private void OnCollisionEnter(Collision c) { // The glue effect (coroutine) provoques multiple calls on same object, // so we need to block this. if (c.gameObject==lastCollidedObject) { return; } lastCollidedObject = c.gameObject; // Get object bouncing properties from object hit // If there are no reflection properties, just let the physics engine do it thing ObjectVelocityModifier2DProperties properties = c.gameObject.GetComponent<ObjectVelocityModifier2DProperties>(); if (properties == null) return; ObjectVelocityModifier2DProperties.Property p = properties.ForTag(gameObject.tag); if (p == null) return; if (p.onIsHit != null) p.onIsHit.Invoke(); // Apply effects DoBounce(c,p); DoGlue(c,p); DoAcceleration(c, p); } /* * Applies the acceleration effect. */ private void DoAcceleration(Collision c, ObjectVelocityModifier2DProperties.Property appliedProperty) { if (!appliedProperty.doAcceleration) return; Vector3 direction=Vector3.zero; switch(appliedProperty.accelerationDirection) { case ObjectVelocityModifier2DProperties.Direction.AWAY: direction = Vector3.right; break; case ObjectVelocityModifier2DProperties.Direction.LEFT: direction = Vector3.forward; break; case ObjectVelocityModifier2DProperties.Direction.RIGHT: direction = Vector3.back; break; case ObjectVelocityModifier2DProperties.Direction.TOFRONT: direction = Vector3.left; break; } myBody.AddForce(Quaternion.AngleAxis(90, direction) * c.contacts[0].normal * appliedProperty.accelerationVelocityFactor, ForceMode.Impulse); if (appliedProperty.onIsAccelerated != null) appliedProperty.onIsAccelerated.Invoke(); } /* * Applies the glue effect. */ private void DoGlue(Collision c, ObjectVelocityModifier2DProperties.Property appliedProperty) { if (!appliedProperty.doGlue) return; IEnumerator coroutine = UnglueAfter(appliedProperty); StartCoroutine(coroutine); } private IEnumerator UnglueAfter(ObjectVelocityModifier2DProperties.Property appliedProperty) { // ** Start-up/Initialisation // Keep original physics velocity velocityBeforeGlue = myBody.velocity; // Documentaion: If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore. // The rigidbody will be under full control of animation or script control by changing transform.position. //Kinematic bodies also affect the motion of other rigidbodies through collisions or joints. myBody.isKinematic = true; myBody.velocity = Vector3.zero; if (appliedProperty.onIsGlued!=null) appliedProperty.onIsGlued.Invoke(); // ** Yield flow yield return new WaitForSeconds(appliedProperty.glueTime); // ** Delayed invocation myBody.isKinematic = false; if (appliedProperty.gluePreserveVelocity) myBody.velocity = velocityBeforeGlue; if (appliedProperty.onGlueReleased!=null) appliedProperty.onGlueReleased.Invoke(); } /* * Applies the bouncing effect. * https://answers.unity.com/questions/580867/issue-using-vector3reflect-to-bounce-a-ball.html */ private void DoBounce(Collision c,ObjectVelocityModifier2DProperties.Property appliedProperty) { ContactPoint cp = c.contacts[0]; if (!appliedProperty.doBounce) return; // Method 1: // Add velocity in direction of hit objects normale multiplied by factor. The object flies then according to physics. // This is the same than method 3. //myBody.velocity = velocityBeforeImpact + cp.normal * p.velocityFactor * velocityBeforeImpact.magnitude; // Method 2: // - Calculate the reflection angle (incoming velocity to touched object normale) // - Add an applification // (not used) myBody.velocity = Vector3.Reflect(velocityBeforeImpact, cp.normal); //myBody.velocity += cp.normal * bounceForce; // Method 3: // Using the physics engine. myBody.AddForce(c.contacts[0].normal * appliedProperty.bounceVelocityFactor, ForceMode.Impulse); } }
Assets/ObjectVelocityModifier2DProperties.cs – Added to each plattform/obstacle, and configured via Inspector
using UnityEngine; using UnityEngine.Events; public class ObjectVelocityModifier2DProperties : MonoBehaviour { [System.Serializable] public enum Direction { LEFT, RIGHT, AWAY, TOFRONT } [System.Serializable] public class Property { // General // ------- [Header("General")] // Addressed specific object tag or none for default [Tooltip("Target object type by tag, or empty to use as default. Specific tag has priority over default.")] public string tagNameOrEmpty=""; // Ignore Behaviour [Tooltip("FALSE to use the bouncing properties, TRUE to apply standard physics and 'neutralise' the bouncing. Same as factor 0.")] public bool ignoreTheseModifiers=false; // Bouncing Behaviour // ------------------ [Header("Bouncing Behaviour")] public bool doBounce = false; [Tooltip("Multiplication of incoming velocity to calculate outgoing bouncing velocity.")] public float bounceVelocityFactor=2f; // Glue Behaviour // -------------- [Header("Glue Behaviour")] public bool doGlue = false; public float glueTime = 2f; [Tooltip("TRUE to preserve velocity from before glue full-stop and restore it afterwards.")] public bool gluePreserveVelocity = false; // Acceleration Behaviour // ---------------------- [Header("Accelerate Behaviour")] public bool doAcceleration = false; public Direction accelerationDirection=Direction.LEFT; [Tooltip("Multiplication of incoming velocity to calculate outgoing acceleration velocity.")] public float accelerationVelocityFactor = 2f; // Callbacks // --------- [Header("Event Callbacks")] public UnityEvent onIsHit; public UnityEvent onIsGlued; public UnityEvent onGlueReleased; public UnityEvent onHasBounced; public UnityEvent onIsAccelerated; } [Header("Object Behaviour Properties")] public Property[] properties; public Property ForTag(string name) { int defaultIndex = -1; int foundIndex = -1; // Search properties for (int i = 0; i < properties.Length; i++) { if (properties[i].tagNameOrEmpty == name) foundIndex = i; if (properties[i].tagNameOrEmpty == "") defaultIndex = i; } // Found a definition for tag if (foundIndex != -1) { if (properties[foundIndex].ignoreTheseModifiers) return null; // This tag is set to not to apply modifiers else return properties[foundIndex]; } // Found a default definition if (defaultIndex != -1) { if (properties[defaultIndex].ignoreTheseModifiers) return null; else return properties[defaultIndex]; } // Found nothing, caller should ignore. return null; } }