// Copyright (C) 2023 Nicholas Maltbie // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and // associated documentation files (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, publish, distribute, // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. using System.Collections.Generic; using nickmaltbie.OpenKCC.Environment.Pushable; using nickmaltbie.OpenKCC.Utils; using UnityEngine; namespace nickmaltbie.OpenKCC.Character { /// /// Basic extension of KCCMovementEngine that allows for /// pushing rigidbody objects that it collides with that /// have the attached MonoBehaviour IPushable. /// [RequireComponent(typeof(Rigidbody))] [RequireComponent(typeof(IColliderCast))] public class KCCMovementEngineWithPush : KCCMovementEngine, ICharacterPush { /// /// Power of the player push. /// [Tooltip("Power of the player push.")] public float pushPower = 4.0f; /// /// Decay in momentum when pushing an object. /// Zero indicates all momentum is lost while 1 indicates all momentum /// is maintained. /// [Range(0, 1)] [Tooltip("Decay in momentum when pushing an object.")] public float pushDecay = 0.8f; /// public bool CanPushObject(Collider hit) { return hit.attachedRigidbody != null && !hit.attachedRigidbody.isKinematic && hit.gameObject.GetComponent() != null; } /// public void PushObject(IControllerColliderHit hit) { // Check if the thing we hit can be pushed Rigidbody body = hit.rigidbody; IPushable pushable = hit.gameObject.GetComponent(); // Do nothing if the object does not have a rigidbody or if // the rigidbody is kinematic if (body == null || body.isKinematic || pushable == null) { return; } // If to the side, use the controller velocity // Project movement vector onto plane defined by gravity normal (horizontal plane) Vector3 force = Vector3.ProjectOnPlane(hit.moveDirection, Vector3.down) * pushPower; Vector3 pushForce = force * pushPower; // Apply the push body.AddForceAtPosition(pushForce, hit.point, ForceMode.Force); pushable.PushObject( pushForce, hit.point, (int)ForceMode.Force); } /// public override IEnumerable GetMovement(Vector3 movement) { foreach (KCCBounce bounce in base.GetMovement(movement)) { if (bounce.action == KCCUtils.MovementAction.Bounce && CanPushObject(bounce.hit?.collider)) { Collider collider = bounce.hit.collider; PushObject(new KinematicCharacterControllerHit( collider, bounce.hit.rigidbody, collider.gameObject, collider.transform, bounce.hit.point, bounce.hit.normal, bounce.initialMomentum.normalized, movement.magnitude )); // If pushing something, reduce remaining force significantly bounce.remainingMomentum *= pushDecay; } yield return bounce; } } } }