☰
从投篮Demo入门Unity3d:物理、碰撞与触发器实战
2026/10/18 21:50:37 网站建设 项目流程

简介:这是基于Unity3D打造的一款篮球游戏Demo,面向Unity初学者与想要了解3D运动游戏实现的开发者。项目中完整演示了篮球游戏的核心玩法:包含篮球场、篮筐、篮球等3D模型,借助物理引擎模拟投篮、碰撞与反弹,通过C#脚本处理玩家输入、得分判定和游戏流程,并集成了动画、音效及UI计分界面。压缩包共1207个文件,以Unity场景(.unity)、预制体(.prefab)、C#脚本(.cs)、FBX/3DS模型、材质贴图(.mat/.png/.jpg)、音频(.mp3)为主要类型,压缩后约46.65MB。目前已有943人学习下载。开发者可以基于该Demo深入理解场景管理、刚体与碰撞、事件触发、动画状态机等Unity核心机制,快速上手小型3D游戏开发的完整流程。 我刚开始用Unity3d那阵儿,和很多人一样,上来就想做一款带球员动画、能打完整比赛的篮球游戏。结果卡在球员运球和投篮动画上整整一周,连球都投不出去。后来我把目标砍到一个“把球投进篮筐”的Unity3d Demo——就是标题里这种简单篮球游戏,反而两天就做出了能玩的东西。

这个Demo的价值不在画面,而在它把Unity3d最常用的几条链路都串起来了:场景搭建、刚体物理、触发器判定、输入交互、UI反馈。如果你现在正拿着Unity3d不知道从哪下手,或者想花一两个晚上做出一个能给别人展示的成果,这个投篮Demo是非常合适的起点。不需要项目源码,不需要美术资产,编辑器自带的Primitive模型就够了。

1. 为什么拿“投篮”当第一个Unity 3D练手项目

1.1 Demo虽小,链路完整

很多人对“简单Demo”有个误解,觉得不值得做。我实际做完才发现,一个投篮场景的难度和体量刚刚好。

先说难度。它比“Hello World”那种纯脚本运动复杂,但又比做完整角色控制器简单。你在里面要处理的核心问题只有一个:怎么让一个带刚体的球,在合适的抛物线轨迹下穿过一个筐。这看起来简单,真正做起来,你会在无意中把Unity3d的物理系统、碰撞器、触发器、相机视角、甚至UI全过一遍。

再说体量。整个场景我用的全是内置物体:一个Plane当地板,一个Sphere当篮球,几个Cube拼成篮板和篮筐,再配一个Canvas显示分数。从打开新项目到能投进第一球,我实际用时大概是两个晚上。这种短周期的正反馈特别重要,尤其是刚开始学引擎的时候,拖得越久越容易弃坑。

所以别小看这个“简单”的定位。它让你能看到一个游戏从零到“能玩”的完整过程,而不是某个孤立的特效或功能。

1.2 引擎选型:为什么是Unity3d而不是UE5

网上关于Unity3d和UE5的对比讨论一直很多。就这个项目而言,选Unity3d的理由非常实际。

UE5强在渲染、Chaos物理、大世界工作流。但用在这个篮球Demo上,就像开着大货车去便利店买瓶水——货箱大是够大,但启动项目、编译shader这些开销,对你调试一个球的物理手感来说完全是多余的。更直白地说,UE5默认的C++和蓝图体系,对新手调试这种“按住鼠标拖一下、球飞出去”的交互,开发节奏明显更重。

Unity3d用的是C#,脚本挂到物体上就能跑,改完参数不用重新编译整个项目,编辑器的Play模式能立刻看到物理表现。你调整Bounciness、拖拽系数时,几乎可以做到“改一改数值,点一下Play”的即时反馈。这种快速迭代对搭建手感非常重要。

另外,这个Demo后续如果要发布到移动端,Unity3d的跨平台导出基本是顺手的事。我也在标题相关热词里看到“unity3d视频流”“webrtc demo”这类方向,说明Unity3d在除了游戏外的实时互动场景也有成熟方案,但那是后话。对一个投篮游戏Demo来说,选Unity3d就是选快速、直观、低门槛。

2. 场景搭建:先把场地和篮筐“立”起来

2.1 用最基础的Primitive搭建场景

先明确一个原则:这个阶段不要纠结好看,先把物理结构搭对。

打开Unity3d新建一个3D项目后,我按这样的层级结构搭场景:

  • Ground:一个Plane,缩放设成(2, 1, 2),放在原点。
  • Basketball:一个Sphere,半径设0.3,放在(0, 1, -5)附近。
  • BasketRoot:一个空物体,专门用来管理篮筐整体,方便整体移动。
    • Backboard:一个Cube,缩放成薄板,放在篮筐后方。
    • RimLeft / RimRight / RimFront / RimBack:四个细长Cube,拼成一个矩形框作为篮圈。
    • ScoreZone:一个空物体,之后放触发器组件。

这种“空物体做根节点,子物体做部件”的组织方式,是Unity3d里的基本功。先把篮筐挂在一个空物体下,后续想整体抬高或移位,直接动BasketRoot就行,不用分别改四个Cube的位置。

篮筐高度我建议放在2.8米左右,也就是Y轴坐标大约2.8。为什么是这个值?真实篮筐高度是3.05米,但Demo里玩家用鼠标拖拽投球,视角往往偏上,稍微低一点可以让命中率更容易被接受,玩起来挫败感小。你要做硬核模拟,再调回3.05也只是一个数字的事。

这里还涉及一个细节:篮球的尺寸。真实篮球直径约24厘米,对应Unity3d单位就是0.24米半径。但Demo里我用0.3米半径,视觉上更明显,也降低命中难度。在练习阶段,先保证“能玩”比“绝对真实”重要。

2.2 物理材质参数和碰撞体的关键设置

光有模型还不够,要让它“像篮球”而不是“像铁块”,物理材质是关键。我在项目中创建了两个Physic Material:

材质名主要参数作用
BallPhysicsDynamic Friction 0.2,Static Friction 0.3,Bounciness 0.7让篮球落地能弹起来,又不会像皮球一样乱跳
RimPhysicsBounciness 0.3,Friction 0.2降低球碰到篮筐后的“弹簧效应”

然后把BallPhysics赋给篮球的Sphere Collider,RimPhysics赋给篮圈四个Cube的Box Collider。

碰撞器这里有一个新手容易踩的坑:篮筐不能用一个大Cube实心碰撞体来做,不然球永远只能撞在筐边上,根本穿不进去。所以篮圈必须由多个细长Cube围成一个空心矩形。细长Cube的推荐截面是0.06 x 0.06米,长度大约1.2米,按前、后、左、右四个方向拼接,形成一个约0.8米见方的框。下方可以再放一个很薄的Cube当篮网底口,但注意底口碰撞体不能把空心区域封死。

最后是篮球的刚体设置。选中Basketball,在Inspector里添加Rigidbody,Mass设1,Drag和Angular Drag都保持默认(0和0.05左右)。最有用的一个参数是Collision Detection,先保持默认Discrete,等后面遇到穿模问题再改,这样你能亲自体会到这个参数的意义,比一开始就改掉要有收获得多。

3. 投篮手感:从鼠标拖拽到抛物线投射的完整链路

3.1 拖拽投球 vs 点击自动投球

我最早做的是“点击按钮自动投球”,就是按一下空格,球自动朝篮筐方向飞。逻辑很简单,但玩起来非常无聊,因为玩家完全没有参与感。

后来改成拖拽投球:按住鼠标左键,向后下方拖拽,松手时球朝鼠标移动的反方向飞出去。这个交互是从愤怒的小鸟那类抛物线游戏借鉴来的,反馈直观:玩家能感受到自己在控制力度和角度,而不是只看一个自动动画。

为什么不直接做成“鼠标点到哪,球就抛到哪”呢?因为那样的话,投篮变成了“瞄准”,而不是“校准力度”,两次投不进之后玩家容易烦躁。拖拽方式里,左右拖控制方向,上下拖控制抛物线高度,玩家有一种“我在调整出手”的感觉,这种微操正是手感的重要来源。

3.2 发射代码与“手感”的调参逻辑

下面是我当时用的一段核心脚本,挂在主相机或者一个空对象上。为了演示方便,我直接在上面引用篮球的Rigidbody:

using UnityEngine; public class BallThrower : MonoBehaviour { public Rigidbody ballRb; public float launchSpeed = 10f; public float backSpinForce = 3f; private Vector3 startMousePos; private bool isDragging = false; void Update() { if (Input.GetMouseButtonDown(0)) { startMousePos = Input.mousePosition; isDragging = true; } if (Input.GetMouseButtonUp(0) && isDragging) { Vector3 endMousePos = Input.mousePosition; Vector3 drag = endMousePos - startMousePos; // 屏幕坐标Y向上,所以取反后玩家往下拖,球往上飞 Vector3 throwDir = new Vector3(drag.x, -drag.y, 8f).normalized; ballRb.velocity = throwDir * launchSpeed; ballRb.angularVelocity = Vector3.left * backSpinForce; isDragging = false; } } }

这个脚本里有几个关键点,值得展开说说。

第一个是方向向量中那固定的“8f”是什么意思。因为篮球放在Z轴负方向,篮筐在Z轴正方向,所以方向向量的Z分量必须保证球会往篮筐方向飞。如果不加这8f,只把鼠标拖拽的二维向量扩展成三维,那么水平拖拽时Z分量可能是0,球就会横着飞出去,砸到旁边的地板上。固定一个Z方向的分量,相当于给玩家一个“默认投向前方”的力,X分量管左右偏转,Y分量管抛物线弧高。这个设计不是物理真实,但手感非常稳。

第二个是launchSpeed的值。我给10,这个值不是随便拍的。它表示球离开手时的初速度是10米/秒。配合Z方向8的固定分量,实际到达篮筐位置大概需要1秒左右。玩家能从视觉上感知到球的飞行时间,不至于快到看不清,也不至于慢到像放慢动作。你可以把它调到8或12试试,低于8球会显得很“飘”,高于12大部分玩家会来不及调整出手点。

第三个是backSpinForce。这给球施加一个绕X轴负方向旋转的角速度,让球在飞行过程中像真实投篮那样向后旋转。没有旋转的球,打到篮筐后反弹方向会很不可控;有了后旋,球碰到篮筐后沿时更容易“蹭”进去,也就是常说的“颠进”。这个细节对进球率影响很大。

4. 计分判定:用Trigger实现不冤枉人的进球检测

4.1 检测区怎么摆

进球判定我一开始用的是碰撞检测,也就是球碰到“篮圈”就得分。结果问题非常大:球打到篮筐边缘弹飞,也算得分;球从底部穿筐,也算得分。后来改成Trigger方案,靠谱得多。

具体操作是:在层级结构里,给BasketRoot再加一个子物体,命名为ScoreZone。给它挂一个Sphere Collider,把Is Trigger勾上。位置放在篮筐中心偏下一点,半径设成0.35米左右,比篮筐的空心区域稍微小一点。

为什么用Sphere而不是Box?因为篮球本身是球体,球进入一个球形触发器区域时,触发边界最自然,不容易出现“半个身子已经穿过筐但还没触发”的错觉。半径0.35米是经验值:比篮球半径0.3米略大,保证只要球的中心点进了筐,就能触发判定,又不至于还没碰到篮筐就提前得分。

4.2 防重复计分与方向校验

如果只写一个OnTriggerEnter就加分数,你会发现一个诡异的问题:球停在篮筐里不掉下去时,分数会一直加,或者球落下来碰到Trigger又弹起来,反复计分。所以必须做两件事:方向校验和冷却。

方向校验的思路是只承认“从上往下掉进筐”的球。在OnTriggerEnter里取球的刚体速度,如果velocity.y小于0,说明球正在下落,才计分。这样一来,球从筐底部被弹上来时即使触发了Trigger,也不会被判分。

private void OnTriggerEnter(Collider other) { if (other.CompareTag("Ball")) { Rigidbody rb = other.GetComponent<Rigidbody>(); if (rb != null && rb.velocity.y < 0f && !canScoreThisBall) { canScoreThisBall = true; score++; StartCoroutine(ResetScoreFlag()); } } }

冷却我用了一个协程。进球后把canScoreThisBall置为true,然后等待0.5秒后重置。这个0.5秒足够覆盖球掉出触发器的过渡时间,又能阻止同一次进球被反复计数。你不用协程,用一个普通的计时变量也行,但协程写法更直观,新手也容易看懂。

还有一个隐藏细节:篮球的Tag要设置成“Ball”。建一个自定义的Tag,然后在进球判定的比较中用CompareTag。因为CompareTag比直接拿字符串比较效率高,也避免拼写错误。很多教程图省事用if (other.name == "Basketball"),一旦篮球克隆出多个实例,名字带了(Clone)后缀,判定立刻失效。用Tag是更稳的做法。

5. 踩坑实录:穿模、弹簧效应与“差一点”的真相

5.1 快球穿筐:物理采样频率的问题

做到这步,你以为投篮功能就完成了,其实还有几个坑在等着。我最先遇到的是穿模:抛出速度调到15之后,球经常像鬼影一样直接从篮筐里穿过去,碰都没碰到。

原因在于Unity3d的物理引擎默认是离散碰撞检测,每物理帧才计算一次碰撞,球速太快时,可能上一帧还在筐的左边,下一帧就已经到了筐的右边,碰撞被直接跳过。解决办法是把篮球Rigidbody的Collision Detection从Discrete改成Continuous Dynamic。这个模式会针对动态物体做连续扫描,防止高速穿透。

但注意一点,Continuous Dynamic不是免费的,性能开销比Discrete大。这个Demo场景里物体少,无所谓;如果你的场景里有几十个高速刚体,就需要控制数量或者用Speculative模式替代。第二个办法是控制发射速度上限。我把launchSpeed上限卡在14,超过这个值,球速带来的问题会成倍增加,而且玩家感知不到明显区别。

5.2 弹簧效应:物理材质与碰撞体厚度的博弈

穿模解决之后,又出现了一个更让人崩溃的问题:球总是卡在篮筐边缘疯狂弹跳,明明已经进了半个球,却怎样都不肯掉下去。

这个现象的根源是物理材质和碰撞体厚度的组合。最初我把BallPhysics的Bounciness设成了0.9,球“弹性十足”,碰到篮筐细长Cube的薄壁时,会获得一个很大的反向反弹速度。又因为篮筐碰撞体很薄,球每次反弹后都重新碰到同一边缘,形成一种“抖抖抖就是进不去”的弹簧效应。

调整方案是双管齐下。先把篮圈Cube的截面从0.04x0.04米加厚到0.08x0.08米,给球一个更平滑的过渡边缘;然后把Bounciness从0.9降到0.5左右。这样球碰到筐边时会有明显的减速和缓冲,而不是像撞到弹簧一样被弹飞。你也可以在篮筐内部加一个朝下的弧形碰撞面,引导球往下落,但Demo阶段用加厚+降低弹性的方案就够了。

5.3 “永远差一点”其实是相机和辅助线的问题

第三个坑是最搞心态的。有时候玩家明明觉得拖拽方向很正,球却总是擦着篮筐边缘飞出去,偏得不多,但就是差一点。我一度以为是碰撞体积的精度问题,排查到最后才发现,问题出在相机视角上。

我用的是正交相机(Orthographic),也就是说画面里没有近大远小,距离感被严重压缩。球飞向篮筐时,玩家无法判断它到底是偏左还是偏右,自然容易“差之毫厘、谬以千里”。换成透视相机(Perspective),把Field of View设成60左右,深度感立刻回来了。

另外,调试时强烈建议在场景视图里用Debug.DrawLine把拖拽方向和球的初速度画出来,这样你一眼就能看出方向向量和实际飞行轨迹的偏差。我在发射脚本里加了一行:

Debug.DrawRay(ballRb.position, throwDir * 10f, Color.green, 2f);

运行游戏后,Scene视图里会显示一条绿色射线。如果射线方向没有对准篮筐,那就说明方向计算有问题;如果射线方向对准了但球还是不进,则要检查球出手时的初始位置是否被其他物体干扰,或者是相机位置导致视觉误差。

6. 不用重新造轮子:这个Demo还能长成什么样子

6.1 计时连击:让玩家有追分乐趣

投篮Demo做到能投能计分,已经算完成了一个核心循环。但如果你把它拿给朋友试玩,大概率对方投两球就走人了。原因是没有目标压力。

给它加一层“压力”其实很容易:加一个60秒倒计时,倒计时结束游戏结算。再加一个连击倍数,连续命中不丢球,分数翻倍。这样玩家会不自觉地在最后10秒疯狂出手,游戏乐趣立刻上升一个档次。

实现上就是用Canvas和TextMeshPro显示倒计时与分数,在Update里做时间递减,在命中回调里更新分数。连击系统则只是多一个consecutiveHits变量,命中时累加,落空时清零。这些都是Unity3d基础操作,但它们能把一个“技术演示”变成一个“有可玩性的游戏”。

6.2 移动端触控与音效

这个Demo如果要发布到手机上,把鼠标输入换成触控非常简单。Input.GetMouseButtonDown和Input.GetMouseButtonUp本身就能响应移动端的触摸事件,Unity3d在这层做了兼容。你只需要注意触控的采样点可能要多点判定,避免误触,然后考虑把篮球数据变成一个Prefab,每次投出前生成一颗新球。

音效也是性价比极高的提升点。进球时播放一声“刷网”的音效,打铁时播放砸框音效,不需要多复杂的音频资源,用Unity3d自带的AudioSource挂到摄像机上,代码里命中时调一下PlayOneShot就行。玩家对声音的反馈比视觉更敏感,这个细节能让Demo的完成度瞬间提升一个档次。

6.3 动态篮筐与难度曲线

如果还想继续扩展,可以让篮筐定期左右移动,或者把投篮距离从近到远分成几个点位,每个点位分值不同。也可以做“随机篮筐出现”模式:场上同时存在两个篮筐,奖励分值不同,玩家需要快速判断投哪个。

这些扩展的共同点是:核心的投篮物理和判定逻辑完全不用改,只需要在合理的位置加一些随机数和UI逻辑。这恰恰说明这个Demo的架构是健康的——它用最简单的结构支撑起了后续所有可能性。

我做完这个投篮Demo以后最大的感受是:一个看似简单的游戏,真正把“手感”调到让人舒服,涉及的细节远超想象。从物理材质参数到相机角度,从触发器半径到碰撞检测模式,每一环都相互影响。Demo不是玩具,它是你把Unity3d物理和交互原理吃透的一座桥。如果哪天你能让一个玩家连续投十分钟不觉得烦,那说明你的引擎基本功已经过关了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询