纯粹 "子弹" 的话. Unity自带的例子 AngryBots 中给出了一种做法.发射时利用射线检测射击到的目标点.直接给予伤害及特效音效表现.而子弹只是作为一道快速的直线飞过.并没有什么判断逻辑.代码如下( JS版 ):
发射 :
// Spawn visual bullet var coneRandomRotation = Quaternion.Euler (Random.Range (-coneAngle, coneAngle), Random.Range (-coneAngle, coneAngle), 0); var go : GameObject = Spawner.Spawn (bulletPrefab, spawnPoint.position, spawnPoint.rotation * coneRandomRotation) as GameObject; var bullet : SimpleBullet = go.GetComponent.<SimpleBullet> (); lastFireTime = Time.time; // Find the object hit by the raycast var hitInfo : RaycastHit = raycast.GetHitInfo (); if (hitInfo.transform) { // Get the health component of the target if any var targetHealth : Health = hitInfo.transform.GetComponent.<Health> (); if (targetHealth) { // Apply damage targetHealth.OnDamage (damagePerSecond / frequency, -spawnPoint.forward); } // Get the rigidbody if any if (hitInfo.rigidbody) { // Apply force to the target object at the position of the hit point var force : Vector3 = transform.forward * (forcePerSecond / frequency); hitInfo.rigidbody.AddForceAtPosition (force, hitInfo.point, ForceMode.Impulse); } // Ricochet sound var sound : AudioClip = MaterialImpactManager.GetBulletHitSound (hitInfo.collider.sharedMaterial); AudioSource.PlayClipAtPoint (sound, hitInfo.point, hitSoundVolume); bullet.dist = hitInfo.distance; } else { bullet.dist = 1000; }
function Update () { tr.position += tr.forward * speed * Time.deltaTime; dist -= speed * Time.deltaTime; if (Time.time > spawnTime + lifeTime || dist < 0) { Spawner.Destroy (gameObject); } }
这里的 够快 指的速度至少是1秒内子弹能飞到目标点.然后伤害表现上稍微做个缓冲效果.就没有问题.
但有时我们想要的"子弹"表现不仅仅是这样快速飞过去的.还可能是稍慢速的炮弹.曲线轨迹的导弹.穿透型的导弹.这样一来预先算好爆炸点的方式就不适用了.只能加Collider做实时碰撞.
在各种尝试之后发现,想使用Unity的RigidBody来控制子弹运动.是不合适的.有以下问题.
1.Unity的物理运动是放在FixUpdate中计算的.与渲染的帧数不同步.会导致物体运动看起来一卡一卡.可以通过设置 Rigidbody.interpolation 为 RigidbodyInterpolation.Interpolate来解决.但发现碰撞检测有延迟.即已经穿透一段距离后才发出碰撞消息.
2.可能穿透物体.
3.碰到后会被撞开.无法做穿透效果.
单个问题来看.
2可以通过设置 Rigidbody.detectCollisions = Continuous来解决
3可以通过设置Rigidbody为IsKinematic为ture.自己控制transform来解决
但一旦设置了IsKinematic.则1和2的设置等于无效.不过因为是自己控制.放在Update中改变位置.则1的问题不复存在.主要是2.
假设认为慢速子弹不存在穿透问题.那么将快速子弹使用射线的方式.慢速子弹使用Collider的方式.似乎就解决了问题.但如果是一个曲线轨迹的快速子弹.似乎两种都不合适.
难道每帧检测飞行方向上的射线?1可能有效率问题.2在曲线拐弯处的路径补偿计算会比较复杂.
综上所述.最后尝试使用了一种动态调整Collider大小的方式来解决以上问题.即根据当前的deltaTime和自身速度来动态调整Collider大小.将自身大小调整到与一帧移动距离相同.则可以避免穿透问题
void ReCalculeteSize( bool bFront ) { BoxCollider bc = gameObject.GetComponent<BoxCollider>(); if( bc == null ) return; float fNewSize = m_BulletType.fSpeed * Time.deltaTime; fNewSize -= 0.4f; if( fNewSize < m_BulletType.fSize ) fNewSize = m_BulletType.fSize; bc.size = new Vector3( m_BulletType.fSize, 5f, fNewSize * transform.localScale.z ); float fNewCenterZ = 0f; if( bFront ) { fNewCenterZ = -fNewSize / 2 + m_BulletType.fSize; if( fNewCenterZ > 0 ) fNewCenterZ = 0f; } else { fNewCenterZ = fNewSize / 2 - m_BulletType.fSize; if( fNewCenterZ < 0 ) fNewCenterZ = 0f; } bc.center = new Vector3( 0f, 0f, fNewCenterZ); }
最后.如果想自己控制position.要设置 IsKinematic 为 true. interpolation 一定要设为None