简介:一份基于Unity3D的篮球游戏Demo,面向刚接触Unity的初学者,也适合想系统梳理交互式3D游戏开发环节的开发者。项目完整覆盖场景构建、物理引擎、角色控制、动画系统、UI计分与音效反馈等核心模块,包内含有主菜单与游戏关卡场景、C#逻辑脚本、预制体、材质与纹理、FBX/3DS模型、MP3音频,以及Unity资源映射与编译配置等文件,共1207个文件,压缩包约46.65MB。已有949人浏览学习。通过分析这个Demo,可以直观看到刚体组件与碰撞器如何让篮球产生真实投篮和反弹效果,脚本如何监听用户输入并驱动投篮动作、分数计算和游戏状态切换,Animator Controller如何播放球员庆祝与投篮动画,UI系统如何实时更新计分板。对于想从零搭建小型3D游戏的人来说,这份Demo提供了完整的可拆解样本,从模型导入、材质调整到发布优化都能一步步对照学习。
1. 一个简单的篮球游戏 Unity3d Demo:打开就能投,但参数才是关键
Unity3d 的篮球游戏 Demo 在工程社区里数量不少,但我拆过的版本里,真正能做到「打开就能投、参数能调、球能进筐」的不多。很多 Demo 的问题不是代码写错,而是物理参数没有配平,要么球直接从篮筐穿过去,要么投篮抛物线完全不成形,最终把体验毁在最后一步。这份资源的价值在于它把投篮闭环完整打通:场景、篮球、篮筐、刚体、碰撞、出手代码、计分判定,每一环都保持简洁,适合拿来当物理玩法的起点。
适合两类人:一类是刚接触 Unity3d、想通过一个完整可跑的 Demo 理解场景搭建、刚体、碰撞器和脚本调用的初学者;另一类是已经在做篮球玩法、想拿一个干净基底改手感、换模型的开发者。如果你只是想找个能直接跑的 Demo 程序过把瘾,这份资源也能让你在一个周末里把整套投篮流程玩个明白。
2. 场景与物理材质:把空场景变成能投篮的球场
大多数人拿到一个 Unity3d 篮球 Demo 后,第一反应是找脚本、看代码,但其实这个 Demo 真正决定「能不能好好投篮」的,是场景搭建阶段的物理材质和 Collider 尺寸。如果你先动手搭场景再谈代码,后面全是顺的;反过来,场景随便摆,再好的函数也救不回来。
2.1 场景结构:相机、灯光、球场的摆放习惯
先从上到下列一个最小可用的对象层级:场景里至少要有灯光、主相机、地面、篮球预制体、篮筐组合体。新手阶段常见的工作流是先拉一个 Plane 或者 Cube 当地面,然后复制几根 Cube 搭篮板和支撑柱,最后把球丢进去。这样做的好处是全程用 Unity3d 内置图元,项目精简、每个部件都能改尺寸,也不用依赖外部三维软件。
我一般习惯把地面设成 12×8 米的 Cube,厚度给 0.2,免得摄像机俯视时地面薄得像纸片。灯光用平行光,旋转建议取 (50, -30, 0),强度 1.0,打开 Shadows。这些数值不需要很精确,但方向性灯光的角度会影响球的阴影投影,阴影能让投篮高度的判断准确很多。很多 demo 程序在场景搭建上偷懒,直接默认平行光,结果球在地上没有影子,出手高度全靠猜。
主相机的位置建议放在 (0, 3, -8) 附近,旋转 (30, 0, 0),也就是大约 30 度俯视整个半场。Field of View 默认 60 就好,太宽会把球拍小,太窄又看不见篮筐。相机的摆放习惯是先对准篮筐,再后退到能同时看到球场边缘和篮筐上部。如果你不想一个旋转就丢失目标,可以只在 Z 轴方向做跟随,具体脚本放到第四章给。
下面这个表是我搭场景时的参数记录,照着填一遍就能得到一个合理的基础环境:
| 对象 | 组件 | 关键参数 |
|---|---|---|
| 主相机 | Camera | 位置 (0, 3, -8),Rotation (30, 0, 0),FOV 60 |
| 平行光 | Light | Rotation (50, -30, 0),Intensity 1.0,Shadows On |
| 地面 | Box Collider | 尺寸 (12, 0.2, 8),位于 (0, 0, 0) |
| 篮板 | Box Collider | 尺寸 (2, 1.2, 0.1),位于 (0, 3.0, -0.45) |
| 支撑柱 | Box Collider | 尺寸 (0.15, 3, 0.15),固定在篮板底部 |
有人会问:为什么地面用 Box Collider 而不是 Plane?球落地必须发生碰撞,Plane 的默认碰撞器是 Mesh Collider,速度高了容易出现边缘卡穿;Box Collider 简单可靠,性能也好。篮板高度我按标准篮球架下沿取 3.0 米,篮筐中心再抬到 3.05 米,出手点大约在 2.2 米左右,这个高度差跟现实投篮习惯是对得上的。
2.2 篮球和篮筐的比例:Collider 尺寸先定死
篮球游戏最容易犯的错,是把球和篮筐的比例取反或不匹配。真实比赛里篮球直径约 24.6 厘米,篮圈内径约 45 厘米,两者比值大约是 1:1.8。在 Unity3d 里如果你把球半径设成 0.3,那篮筐的内径就不能小于 0.55,否则命中概率会变得极不真实。我一般把球半径固定为 0.3,篮筐内径做到 0.58,留一点点盈余,投篮判定会舒服一些。
篮筐不要直接用一个环状网格加 Mesh Collider。Torus 模型的 Mesh Collider 在球高速运动时很容易出现碰撞缝隙,而且 Mesh Collider 不勾 Convex 时刚体不能正常碰撞。常见的做法是用四段细长的 Box Collider 拼成一个空心矩形,内径按球半径的约两倍来控制。这个方案虽然听起来土,但碰撞最稳,调试也最直观,球的 Sphere Collider 半径跟 Mesh 保持一致就好。
如果你以后想把自己做的篮球模型替换进去,记得从 Blender 或者 SolidWorks 导出 FBX 时把单位改成米。SolidWorks 模型导入 Unity3d 时很容易出现 100 倍偏差,因为 SolidWorks 默认单位是毫米;导出前把单位设为米,或者在 Unity3d 导入设置里把 Scale Factor 调成 0.01。单位错了的后果就是篮筐巨大无比,球穿过去就像不存在一样。这一步属于模型导入阶段最常踩的坑。
2.3 物理材质:为什么球的弹性和摩擦要单独建
默认的 Physic Material 什么都没有,球砸到地面会像石头一样弹不起来,或者砸到篮筐边缘直接停在上面。新建一个名为 BallPhysics 的 Physic Material:Dynamic Friction 设 0.4,Static Friction 设 0.4,Bounciness 设 0.7,Friction Combine 设 Average,Bounce Combine 设为 Maximum。Bounce Combine 用 Maximum 是为了让球在碰到篮筐边缘时保留最大弹性,不会因为摩擦设置而抵消反弹。
篮筐和篮板则单独用一个 LowBounce 材质:Bounciness 设 0.1。否则球打板后会带上篮板的高弹性,直接弹出很远,投篮体验和现实严重不符。物理材质设置是这个 Demo 的隐藏关键点。很多时候你觉得“这球出手怎么这么怪”,去查代码查半天,其实问题就是 Bounciness 配错了。先把材质建好,再进 Play Mode 试投几球,你会发现球的反弹轨迹基本是符合直觉的。
| 材质名 | Dynamic Friction | Static Friction | Bounciness | Bounce Combine |
|---|---|---|---|---|
| BallPhysics | 0.4 | 0.4 | 0.7 | Maximum |
| LowBounce | 0.6 | 0.6 | 0.1 | Minimum |
有人会问为什么 Bounciness 需要单独建材质而不是直接在 Collider 上调,因为 Collider 上没有弹性参数,Unity3d 把所有碰撞物理都统一到 Physic Material 上,你只能建材质再挂到 Collider。这个设计很多人第一次用会忽略,所以特别提一下。物理材质一旦挂上,球落地后的弹跳次数和力度就会变得可控,不会出现一颗球在地板上弹到怀疑人生的场面。
3. 投篮物理与碰撞检测:Rigidbody 参数怎么配才不乱飞
场景搭完之后,下一个决定成败的点是球体上的 Rigidbody。很多人拿到这份资源后直接进 Play Mode 乱调,结果球乱飞,其实只要把刚体参数和投篮施力方式理解了,它完全是可控的。我先把最小可用参数摆出来,再解释为什么这样设。
3.1 刚体、质量、重力与手感
给篮球挂上 Rigidbody 后,先不要动脚本,先把这个参数表填好:
| 参数 | 值 | 说明 |
|---|---|---|
| Mass | 1 | Unity 物理是比例制,新手固定用 1 免得换算 |
| Drag | 0.2 | 空气阻力,太大球会飘,太小球像冰壶 |
| Angular Drag | 5 | 让球的旋转不至于永远停不下来 |
| Use Gravity | 勾选 | 没有重力就没有抛物线 |
| Collision Detection | Continuous | 防止高速穿过碰撞体 |
| Interpolate | Interpolate | 让高速运动在渲染上平滑,减少抖动 |
| Constraints | Freeze Rotation X/Z | 篮球只绕横轴自旋,避免侧向翻滚 |
Mass 为什么设 1 而不是真实篮球的 0.6kg?因为 AddForce 的冲量计算是力乘以时间,Mass 越小同样冲量产生的速度变化越大。在 Demo 阶段直接用 1,后续调力度就好;如果一开始就设成真实质量,力度范围也要对应缩水,平白多一层换算。等手感定了再回头改 Mass 也不迟。
Drag 是球出手后速度衰减的元凶,设成 0.2 是为了让球在飞行 8 米后仍保有足够速度,不至于后半程忽然掉速。Angular Drag 设 5 是给自旋一个阻尼,防止球进筐后还在里面疯狂转圈停不下来。这里有一个很多人忽略的点:Interpolate 默认是 None,球在高速飞行时会轻微颤抖,帧率不稳定的时候尤其明显。这个参数不改变物理,只改变渲染插值,但开不开完全影响手感。我一般第一次调 Demo 就先勾上 Interpolate,省得后面以为是脚本问题。
3.2 初速度怎么算:角度和力度拆成 X 与 Y 分量
投篮抛物线说到底是两个分量的叠加:水平方向匀速,竖直方向匀减速。设出手点高度为 h0,篮筐高度 h1,水平距离为 d,出手角度为 θ,那么水平分速度 vx = v·cos(θ),竖直分速度 vy = v·sin(θ),飞行时间 t = d / vx,竖直位移满足 h1 - h0 = vy·t - 0.5·g·t²。把这个式子整理一下,就能用已知量反解需要的初速度 v。
在 Unity3d 里可以写一个小函数,传入出手点、目标点、角度和重力,返回一个速度向量。下面这个实现常见做法是把 gravity 取 Physics.gravity.magnitude,目标点取篮筐入口上方 0.15 米处:
using UnityEngine; public static class BallPhysicsHelper { public static Vector3 ComputeVelocity(Vector3 from, Vector3 to, float angleDeg, float gravity) { Vector3 dir = to - from; float horizontal = new Vector2(dir.x, dir.z).magnitude; float rad = angleDeg * Mathf.Deg2Rad; float cos = Mathf.Cos(rad); float tan = Mathf.Tan(rad); float denominator = 2f * cos * cos * (horizontal * tan - dir.y); if (denominator <= 0f) { Debug.LogWarning("当前角度无法命中目标,请减小角度或抬高出手点"); return Vector3.zero; } float v2 = gravity * horizontal * horizontal / denominator; float v = Mathf.Sqrt(v2); Vector3 hDir = new Vector3(dir.x, 0f, dir.z).normalized; return hDir * (v * cos) + Vector3.up * (v * Mathf.Sin(rad)); } }这段代码的核心是:先由两点的水平投影距离算出水平分量需要多大,再根据角度和高度差反推总初速度 vr,最后把速度拆成水平向量和竖直上抛向量。denominator 小于等于 0 说明这个角度在当前的抛物线高度差下不可能命中,直接打警告比返回一个奇怪负数让你瞎猜强得多。
角度取多少合适?实测 45 度到 55 度之间最稳,角度太小球容易打前沿,太大球会砸到篮板下沿。我一般固定 50 度,然后把力度留成可调节变量,手感联调时不用反复改物理公式。在 AddForce 使用 Impulse 模式下,把速度向量乘上 rb.mass 再传入,就能得到你想要的那个初速度。
3.3 碰撞检测与 CCD:球为什么会穿筐
即使 Collider 尺寸都正确,球速度超过一定阈值后,物理引擎还是会让碰撞体穿过另一层碰撞体。这是因为默认的 Collision Detection 是 Discrete,碰撞检测只发生在固定时间步的采样点,两帧之间穿过一块薄板时,引擎根本检测不到相交。
要让球不穿筐,最直接的做法是打开球的 Continuous 碰撞检测。在 Rigidbody 的 Collision Detection 选项里,Discrete、Continuous、Continuous Speculative 三档,Continuous 会对静态碰撞体做连续扫描,球再快也拦得住。代价是 CPU 开销更高,但 Demo 里只有一颗球,完全承担得起。
开启 CCD 之后,可以做一次验证。把球放在篮筐上方 1 米处,给一个向下的初速度,然后用 OnCollisionEnter 打印碰撞日志:
void OnCollisionEnter(Collision collision) { Debug.Log("碰撞到: " + collision.gameObject.name + " 相对速度: " + collision.relativeVelocity.magnitude); }如果日志里一直不出现篮筐的名字,说明仍旧穿模,那优先检查篮筐的 Collider 是不是 Mesh Collider 且没勾 Convex。四段 Box 拼出来的篮筐几乎不会出现这个问题,这也是我在上一节推荐拆成 Box 的原因。另外还有一个细节:开启 CCD 后,Constraints 不要锁死 Y 轴旋转,否则球碰筐时的姿态会非常假。锁住 X/Z 的翻滚,保留 Y 轴旋转,球在筐里滚动和弹出的观感才自然。
4. 投篮脚本与输入控制:鼠标蓄力投篮的完整实现
场景和物理参数准备好之后,剩下的核心是把「鼠标点击」变成「出手力度」。很多 Unity3d 篮球 Demo 在这里抄近路,单击就投,结果玩家完全没有控制手感的机会。我建议把输入方案设计成按住鼠标蓄力、松开鼠标投篮,这样你在试玩时能感受到力度和抛物线之间的真实关系。
4.1 输入方案:按住蓄力、松开投篮的交互设计
为什么不用单击:单击投篮虽然代码少,但力度不可控,所有投篮都长一个样,玩几球就腻。蓄力方案包含三个关键事件:按下鼠标开始蓄力,按住期间定时增加力度并做上限截断,松开鼠标把当前力度用于生成球。这个方案只需要用到 Input.GetMouseButtonDown、Input.GetMouseButton 和 Input.GetMouseButtonUp,没有任何平台依赖,鼠标和触摸屏都能用。
在结构上,投篮脚本只负责生成球和计算方向,不做物理参数微调。球的刚体参数、材质全部放在预制体里,这样每次 Instantiate 出来的球天然带上正确的 Rigidbody 和 Physic Material。这是很多 demo 程序容易忽略的点:运行时新生成的球没有材质,物理表现和预置球完全不同,你还以为是脚本手滑。
4.2 投篮脚本主体代码
下面这份脚本可以直接挂到一个空对象或者 Camera 上。它的职责:读取鼠标输入,生成篮球预制体,计算方向并施加冲量。
using UnityEngine; public class PlayerShoot : MonoBehaviour { [Header("篮球预制体与出手点")] public GameObject ballPrefab; public Transform shootPoint; public Camera mainCamera; [Header("蓄力参数")] public float maxForce = 800f; public float chargeSpeed = 280f; private float currentForce = 0f; private bool isCharging = false; void Update() { if (Input.GetMouseButtonDown(0)) { isCharging = true; currentForce = 0f; } if (isCharging && Input.GetMouseButton(0)) { currentForce += chargeSpeed * Time.deltaTime; currentForce = Mathf.Clamp(currentForce, 0f, maxForce); } if (Input.GetMouseButtonUp(0) && isCharging) { ShootBall(currentForce); isCharging = false; currentForce = 0f; } } void ShootBall(float forceValue) { GameObject ball = Instantiate(ballPrefab, shootPoint != null ? shootPoint.position : transform.position, Quaternion.identity); Rigidbody rb = ball.GetComponent<Rigidbody>(); if (rb == null) return; // 防御代码:每次生成的球都强制使用标准物理参数 rb.useGravity = true; rb.drag = 0.2f; rb.angularDrag = 5f; Vector3 dir = mainCamera.transform.forward; dir.y += 0.15f; dir.Normalize(); rb.AddForce(dir * forceValue, ForceMode.Impulse); } }逻辑说明:Update 的三个 if 对应三条输入分支。按下鼠标那帧把力度归零;按住期间每帧叠加 chargeSpeed 乘以时间步长,再用 Clamp 夹到 maxForce 以内;松开时调用 ShootBall,创建球、读取刚体、计算方向、施加冲量。
参数说明:maxForce 和 chargeSpeed 是这份脚本里需要反复调的两个数值。在 Impulse 模式下,AddForce 的冲量换算成速度增量大约等于 force × fixedDeltaTime ÷ mass。Unity 默认 fixedDeltaTime 是 0.02 秒,质量 1 kg 时,800 的力度大约等效 16 m/s 的初速度,投 8 米外的篮筐是够的。按住 3 秒蓄满,节奏偏慢偏稳;想快节奏就把 chargeSpeed 提到 400 以上。场景如果整体放大到两倍,maxForce 也要跟着放大,线性关系,不用改别的。
方向计算这里值得强调:mainCamera.transform.forward 是相机正前方,先给 y 分量加 0.15 抬高出射角,再 Normalize,得到的是标准方向向量。如果你希望球有更高的抛物线,把 0.15 改成 0.25,但不要超过 0.3,否则会变成高射炮,球在空中停留时间太长。shootPoint 建议在场景里创建一个空物体,放在相机前下方大约 (0, -0.5, 2) 的位置,再拖进脚本字段;如果直接留空,球会出现在相机位置,有可能被相机近裁面挡掉。
4.3 相机跟随与视角稳定性
投篮方向取自主相机的前方,因此相机稳定性直接决定投篮手感。最简单的固定视角相机方案是:相机不跟随任何人,只摆在地面中线一侧的上方。但这意味着球落下后不在画面中央,试投几球你就想调整视角了。
我建议用 LateUpdate 做一个轻量跟随,不引入任何平滑逻辑:
using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public Vector3 offset = new Vector3(0f, 4f, -7f); void LateUpdate() { if (target == null) return; transform.position = target.position + offset; transform.LookAt(target); } }LateUpdate 的作用是让相机在物理和动画更新完之后再改位置,避免画面抖动。这里没有使用 SmoothDamp 或任何插值,原因是投篮类玩法需要稳定的瞄准参考,相机轻微滞后的“丝滑感”反而会干扰你对出手方向的判断。target 不要设成篮球本身,而是在场景里放一个固定锚点,比如投篮点前方 1 米的地面空物体,否则球一生成相机就追着球跑,出手瞬间的方向就被带偏了。偏移向量的 y 值如果太小,近距离投篮视线会被篮板挡住,可以加到 5;x 偏移保持 0,让视角绝对正对。
到了这一步,打开 Play Mode,按住鼠标蓄力、松开投篮,你应该能看到完整的抛物线。如果球总是投不到目标,先别急着改脚本,回去检查 ballPrefab 的 Rigidbody 是否勾了 UseGravity,以及场景中是否有一个残留的老球带走了力和方向。这个排查习惯能帮你省掉大量调参时间。
5. 避坑与排查:篮球 Demo 里最容易翻车的五个点
任何物理相关的 Unity3d Demo 都难逃翻车时刻,篮球游戏尤其集中。原因很简单:涉及刚体、碰撞器、触发器、相机四个系统,任何一个环节出错,表现都是投篮不正常。下面的五条都是真金白银的血泪经验,按现象到原因再到解决记录,后续你照着目录排查即可。
5.1 物理与碰撞类的坑:穿筐、乱飞、悬空
坑一:球直接从篮筐中间穿过去,篮筐好像不存在。
现象:球沿抛物线到达篮筐位置,但没有停顿、没有反弹,直接穿过后继续下落。
原因:最常见的是篮筐的网格模型没有挂任何 Collider,或挂的是 Mesh Collider 且没勾 Convex;其次是球速度过快,Rigidbody 默认 Discrete 碰撞检测采样间隔太大,把薄板碰穿了。
解决:篮筐用四段 Box Collider 拼装,球的 Collision Detection 选 Continuous。如果球速在 Demo 里非常快,可以再选 Continuous Speculative。跑一次带 Debug.Log 的 OnCollisionEnter 确认碰撞事件真的被触发。
坑二:球出手后完全不成抛物线,要么像起步飞车,要么往侧上方乱窜。
现象:球的运动方向跟相机朝向完全对不上,甚至出现球往方向向量乘以几十倍之后乱飞的情况。
原因:大概率是投篮脚本里用了 Vector3.forward 而不是相机 forward,或者没有先 Normalize 就把方向乘上了大力。Vector3.forward 是世界坐标的 Z 轴正方向,在你旋转过相机后它指向的并不是屏幕前方。
解决:统一用 mainCamera.transform.forward,先复制到局部变量,做完 y 偏移后 Normalize,再乘力度。把调试台打开,观察球生成时 transform.forward 的值,能很快找出问题。
坑三:球飞到一半悬空,或者一直在一个高度上飘。
现象:球刚出手时速度正常,几秒后速度衰减到几乎为零,停在半空慢慢下滑。
原因:Rigidbody 的 Use Gravity 没勾,或者 Drag 被设成很大的值。很多 demo 程序在预制体里会复制一个非物理球,忘了刚体参数没挂全。
解决:在 PlayerShoot.ShootBall 里加防御代码,每次生成的球都强制设置 useGravity、drag、angularDrag。这行代码看起来冗余,但能避免预制体被意外改坏后,你要花一晚上排查“为什么只有第一次投篮是正常的”。
5.2 玩法与判定类的坑:计分错乱、视角乱动
坑四:球砸到篮板也加分,碰一下篮筐边沿也加分,进球与否完全对不上。
现象:得分次数明显比命中次数多,很多没进筐的球也触发了加分。
原因:计分判定直接写在 OnCollisionEnter 里,而碰撞事件包含所有接触,包括撞篮板、撞筐沿。此外如果篮筐整体是一个实心碰撞体,球与其表面任何一点接触都会被当作计分。
解决:计分判定必须和物理碰撞分离。物理碰撞仍由实体 Collider 负责;计分单独在篮筐中心上方做一个触发区域。新建一个半径为 0.35 的 Sphere Collider,位置在篮筐上沿 0.15 米,勾选 Is Trigger,挂单独的得分脚本。进球时球先从实体筐边弹进去,再进入 Trigger,触发 OnTriggerEnter,这才算分。
坑五:按住鼠标蓄力时相机在抖动,或者球的方向跟鼠标位置不匹配。
现象:投篮前想观察角度,结果相机会跟着球的出生点或者鼠标位置自动旋转,瞄准线一直在晃。
原因:相机跟随脚本用了 SmoothDamp,或者把鼠标 X/Y 轴映射到了相机旋转。物理投篮的抛物线只由相机 forward 决定,相机旋转不稳定,方向就无法预测。
解决:投篮 Demo 的相机用固定俯仰角加固定偏移,也就是上一章的 CameraFollow,去掉所有旋转跟随。以后如果确实想要鼠标转向,投篮方向就别从相机 forward 取,而是从 Camera.main 的中心点打一条 ScreenPointToRay,用射线方向来计算落点。两条路线只能选一条,不能混用。
还有一个容易被忽略的问题是旧球残留。如果 Instantiate 生成的球不销毁,投了 20 次之后场景里有 20 颗球,每颗球都带 Rigidbody 和 Collider,物理运算会越来越重,帧率下降会直接导致碰撞判定卡顿。简单做法是在生成第 10 颗球时自动销毁最早的球,或者给球挂一个 10 秒后自动 Destroy 的协程。这个不是功能问题,但会很快变成手感问题。
6. 进阶:计分判定与后旋球,把 Demo 做成像样的篮球游戏
基础投篮通顺后,最后一步是让 Demo 更像一个“篮球游戏”,而不是一颗球乱飞的物理沙盒。我挑了两个性价比最高的加法:精确的进球判定和后旋球。
进球判定不能依赖 OnCollisionEnter,正如第五章所说,要用 Trigger。但 Trigger 的位置和形状需要特别注意:放在篮筐中心正上方,半径比篮筐内径略小,高度控制在 0.2 米左右。球进入这个区域才触发加分,相当于一个虚拟的“篮网判定器”。下面是计分逻辑的完整脚本:
using UnityEngine; public class ScoreTrigger : MonoBehaviour { [SerializeField] private int score = 0; public string targetTag = "Ball"; private void OnTriggerEnter(Collider other) { if (other.CompareTag(targetTag)) { score++; Debug.Log("命中! 当前比分: " + score); } } }逻辑说明:挂在 Trigger 的 Sphere Collider 上,OnTriggerEnter 判断进入触发器的物体是否带 Ball 标签,带则加分。targetTag 暴露到编辑器里,方便以后把球标签改成其他名字。注意这个脚本只依赖标签,不依赖球的刚体参数,任何物体只要带 Ball 标签都会被计分。
第二个技巧是后旋球。现实里射手出手后球是向后旋转的,入筐碰到筐沿时后旋会把球“带”进篮筐,而不是弹飞。用 AddTorque 实现非常直接,在 PlayerShoot.ShootBall 里加上一行:
rb.AddTorque(new Vector3(4f, 0f, 0f), ForceMode.Impulse);Vector3(4, 0, 0) 是绕 X 轴做一个 4 单位冲量的旋转力矩。正 X 方向会让球从下往上向后翻滚,也就是投篮球一贯的后旋姿态。强度在 Demo 尺寸下表现比较明显;加太大球会在空中乱翻,反而削弱碰撞进球的概率。
如果你想换掉默认篮球模型,比如把自己从 SolidWorks 导出的球模型放进来,Unity3d 导入时注意单位问题。SolidWorks 默认毫米,导出 FBX 时如果忘记改单位,导入后球体变成真实尺寸的 100 倍,直接把整个场景撑爆。在导入设置里将 Scale Factor 设为 0.01,或者导出时直接选“米”单位,球的大小才能和你现有的 Collider 参数匹配。模型替换后重新检查 Sphere Collider 半径即可,其余脚本不用动。
做完这套,这个 Unity3d 篮球 Demo 才算真正闭环:场景、物理、出手、计分、旋转、模型替换全部打通。把这份资源下载到本地,按上面的章节一步步复现,你会比我当初第一次调物理时少走很多弯路。从那以后我每次拿到一个物理类的 Unity3d Demo,都会先把 Collider 尺寸、Rigidbody 的质量和 AddForce 力度记在纸上再开始调玩法。以前总觉得手感是玄学,全靠乱调,实际拆得多了才发现,大多数翻车的坑都来自这些基础参数。把基础参数钉死,后面的玩法设计才有意义。希望帮到你。
本文还有配套的精品资源,点击获取