脚本分别实现了三个功能,直线运动,抛物线运动,曲线运动,直线和抛物线从俯视图看能看出效果,曲线的效果得从正前方的摄像视角看,才看得出。如果要改变摄像机视角,比如要从正前方跟随人物,侧面视角跟随人物等等,只要确保鼠标点击屏幕的坐标点被转换出来的坐标是正确的即可,看得懂的小伙伴可以自行改动哈!主页写了足球网的物理效果,搭配足球的脚本可以模拟出足球射进球网时的效果
素材保密哈,Ceshi就是Ball脚本,我的摄像机是俯视看着球的,如果小伙伴在测试当中,跟我差不多设置的话,球能发送出去,但发射位置不对大概率是屏幕点击转换坐标的问题
下面是球的设置,只需要拉到主摄像机即可,其他参数随便调
下面是摄像机的参数设置
下面是脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour
{
public Camera mainCamera;
public LaunchType currentLaunchType = LaunchType.Straight; // 当前发射类型
public float forceMultiplier = 1.5f; // 力的倍率
public float launchAngle = 45f; // 发射角度
public float straightSpeed = 10f; // 直线速度
public float curveForce = 1f; // 曲线力
public float curveDuration = 1f; // 曲线持续时间
public float spinForce = 20f; // 旋转力
public float drag = 0.5f; // 空气阻力
public float angularDrag = 0.5f; // 角阻力
private LineRenderer pathLineRenderer; // 用于实际路径显示的线渲染器
private new Rigidbody rigidbody; // 刚体组件
private Coroutine curveCoroutine; // 曲线运动协程
private Vector3 targetPosition; // 目标位置
private void Start()
{
// 获取Rigidbody组件并设置阻力
rigidbody = GetComponent<Rigidbody>();
rigidbody.drag = drag; // 设置刚体阻力
rigidbody.angularDrag = angularDrag; // 设置刚体角阻力
// 初始化pathLineRenderer
if (transform.GetChild(0).gameObject.GetComponent<LineRenderer>() == null)
{
pathLineRenderer = transform.GetChild(0).gameObject.AddComponent<LineRenderer>();
pathLineRenderer.widthMultiplier = 0.1f; // 设置线的宽度
pathLineRenderer.material = new Material(Shader.Find("Sprites/Default")); // 设置线的材质
pathLineRenderer.startColor = Color.cyan;
pathLineRenderer.endColor = Color.blue;
}
pathLineRenderer.positionCount = 0; // 初始点数为0.
}
private void Update()
{
// 更新目标位置
UpdateTargetPosition();
}
// 更新目标位置
private void UpdateTargetPosition()
{
// 使用鼠标点击获取目标位置
if (Input.GetMouseButtonDown(0))
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hitInfo))
{
switch (currentLaunchType)
{
case LaunchType.Parabolic:
SetFootballParabolic(hitInfo.point);
break;
case LaunchType.Straight:
SetFootballStraight(hitInfo.point);
break;
case LaunchType.Curved:
SetFootballCurved(forceMultiplier,launchAngle,curveForce,curveDuration, hitInfo.point);
break;
default:
break;
}
}
}
}
// 带球
public void Dribbling(float speed)
{
transform.Rotate(Vector3.right * speed); // 让自身向上自转
}
// 抛物线
public void SetFootballParabolic(Vector3 targetTransform, float _forceMultiplier = 0, float _launchAngle = 0)
{
float distance = Vector3.Distance(targetTransform, transform.position);
float forceMultiplier = 0, launchAngle = 0;
if (distance < 20)
{
forceMultiplier = 1.7f + _forceMultiplier;
launchAngle = 25f + _launchAngle;
}
if (distance > 20 && distance < 30)
{
forceMultiplier = 1.7f + _forceMultiplier;
launchAngle = 20f + _launchAngle;
}
if (distance > 30 && distance < 50)
{
forceMultiplier = 1.7f + _forceMultiplier;
launchAngle = 45f + _launchAngle; ;
}
if (distance > 50)
{
forceMultiplier = 1.6f + _forceMultiplier;
launchAngle = 35f + _launchAngle;
}
//以上的抛物线计算力度和角度只是简单的,没有用到算法计算
SetFooballParameter(forceMultiplier, launchAngle, 0, 0, 0, 20);
ShootingBallMethod(LaunchType.Parabolic, targetTransform);
}
// 直线
public void SetFootballStraight(Vector3 targetTransform, float _straightSpeed = 0)
{
float distance = Vector3.Distance(targetTransform, transform.position);
float speed = ((distance / 3f) * 1.8f) + ((drag * 10) + (angularDrag * 10)) + _straightSpeed;
SetFooballParameter(0, 0, speed, 0, 0, 20);
ShootingBallMethod(LaunchType.Straight, targetTransform);
}
// 曲线
public void SetFootballCurved(float _forceMultiplier, float _launchAngle, float _curveForce, float _curveDuration, Vector3 targetTransform)
{
SetFooballParameter(_forceMultiplier, _launchAngle, 0, _curveForce, _curveDuration, 20);
ShootingBallMethod(LaunchType.Curved, targetTransform);
}
// 设置参数
private void SetFooballParameter(float _forceMultiplier, float _launchAngle, float _straightSpeed, float _curveForce, float _curveDuration, float _spinForce)
{
this.forceMultiplier = _forceMultiplier;
this.launchAngle = _launchAngle;
this.straightSpeed = _straightSpeed;
this.curveForce = _curveForce;
this.curveDuration = _curveDuration;
this.spinForce = _spinForce;
}
// 发射球
private void ShootingBallMethod(LaunchType launchType, Vector3 targetTransform)
{
currentLaunchType = launchType;
targetPosition = targetTransform;
// 计算目标方向并忽略Y轴
Vector3 direction = targetPosition - transform.position;
direction.y = 0;
Vector3 velocity = Vector3.zero;
string positionRelativeToBall = DetermineClickPosition(direction.normalized); // 获取发射点的位置
// 根据发射类型计算初速度
switch (currentLaunchType)
{
case LaunchType.Parabolic:
velocity = CalculateVelocity(transform.position, targetPosition, launchAngle) * forceMultiplier;
break;
case LaunchType.Straight:
if (direction.magnitude > 0)
{
direction.Normalize();
velocity = direction * straightSpeed;
}
else
{
Debug.LogWarning("Direction vector magnitude is zero. Aborting straight kick.");
}
break;
case LaunchType.Curved:
if (direction.magnitude > 0)
{
direction.Normalize();
velocity = CalculateVelocity(transform.position, targetPosition, launchAngle) * forceMultiplier;
}
else
{
Debug.LogWarning("Direction vector magnitude is zero. Aborting curved kick.");
}
break;
}
// 检查是否计算出有效的速度
if (float.IsNaN(velocity.x) || float.IsNaN(velocity.y) || float.IsNaN(velocity.z))
{
Debug.LogError("Calculated velocity is NaN. Aborting kick.");
return;
}
// 设置刚体速度并启动轨迹协程
rigidbody.velocity = velocity;
pathLineRenderer.positionCount = 0;
StartCoroutine(TrailEffect());
// 如果是曲线发射类型,启动曲线运动协程
if (currentLaunchType == LaunchType.Curved)
{
if (curveCoroutine != null)
{
StopCoroutine(curveCoroutine);
}
curveCoroutine = StartCoroutine(ApplyCurve(positionRelativeToBall, velocity));
}
// 应用旋转力
ApplySpin(positionRelativeToBall);
}
// 曲线运动协程
private IEnumerator ApplyCurve(string clickPosition, Vector3 initialVelocity)
{
Vector3 curveDirection = Vector3.zero;
if (clickPosition == "right")
{
curveDirection = Vector3.left;
}
else if (clickPosition == "left")
{
curveDirection = Vector3.right;
}
float elapsedTime = 0f;
while (elapsedTime < curveDuration)
{
rigidbody.AddForce(curveDirection * curveForce, ForceMode.Acceleration);
elapsedTime += Time.deltaTime;
yield return null;
}
Debug.Log($"Curved Final Velocity after curve force: {rigidbody.velocity}");
}
// 应用旋转力 模拟球旋转
private void ApplySpin(string clickPosition)
{
Vector3 spinDirection = Vector3.zero;
switch (currentLaunchType)
{
case LaunchType.Parabolic:
spinDirection = Vector3.forward;
rigidbody.AddTorque(spinDirection * spinForce * 2.0f, ForceMode.Impulse);
break;
case LaunchType.Straight:
spinDirection = Vector3.up;
rigidbody.AddTorque(spinDirection * spinForce * 1.5f, ForceMode.Impulse);
break;
case LaunchType.Curved:
if (clickPosition == "right")
spinDirection = Vector3.down;
else if (clickPosition == "left")
spinDirection = Vector3.up;
rigidbody.AddTorque(spinDirection * spinForce, ForceMode.Impulse);
break;
}
//Debug.Log($"Applied Spin: {spinDirection * spinForce}");
}
// 计算发射速度
private Vector3 CalculateVelocity(Vector3 startPoint, Vector3 endPoint, float angle)
{
float gravity = Physics.gravity.magnitude; // 获取重力的模拟值
float radianAngle = angle * Mathf.Deg2Rad; // 角度转弧度
float horizontalDistance = Vector3.Distance(new Vector3(startPoint.x, 0, startPoint.z), new Vector3(endPoint.x, 0, endPoint.z)); // 计算起点和终点在水平面上的距离
float heightDifference = endPoint.y - startPoint.y; // 计算起点和终点的高度差
// 计算初速度
float initialVelocity = (1 / Mathf.Cos(radianAngle)) * Mathf.Sqrt((0.5f * gravity * Mathf.Pow(horizontalDistance, 2)) / (horizontalDistance * Mathf.Tan(radianAngle) + heightDifference));
Vector3 velocity = new Vector3(0, initialVelocity * Mathf.Sin(radianAngle), initialVelocity * Mathf.Cos(radianAngle)); // 初速度向量
Vector3 launchDirection = (new Vector3(endPoint.x, 0, endPoint.z) - new Vector3(startPoint.x, 0, startPoint.z)).normalized; // 发射方向
Vector3 finalVelocity = new Vector3(launchDirection.x * velocity.z, velocity.y, launchDirection.z * velocity.z); // 最终速度向量
return finalVelocity;
}
// 确定点击位置相对球的位置,判断方向
private string DetermineClickPosition(Vector3 clickpoint)
{
// 计算点击方向向量
Vector3 clickDirection = clickpoint - transform.position; //parent
clickDirection.y = 0; // 忽略y轴
// 获取物体前向
Vector3 forward = transform.forward; //parent
forward.y = 0; // 忽略y轴
// 计算角度
float angle = Vector3.SignedAngle(forward, clickDirection.normalized, Vector3.up);
// 根据角度判断点击方向
string direction = GetDirection(angle);
Debug.Log("Click direction: " + direction);
return direction;
}
string GetDirection(float angle)
{
// 如果角度接近 0 或接近 180,则认为是中心
if (Mathf.Abs(angle) < 10 || Mathf.Abs(angle) > 170)
{
return "center";
}
else if (angle > 0)
{
return "right";
}
else
{
return "left";
}
}
// 轨迹效果协程
private IEnumerator TrailEffect()
{
float trailTime = 10f;
float startTime = Time.time;
while (Time.time < startTime + trailTime)
{
pathLineRenderer.positionCount++;
pathLineRenderer.SetPosition(pathLineRenderer.positionCount - 1, rigidbody.position);
yield return null;
}
pathLineRenderer.positionCount = 0;
}
}