A very common game behavior for the player (or a computer-controlled non-player-character (NPC)) is to fire a projectile in the direction they are facing. This recipe presents a way to achieve this behavior.
Although there are several steps in this recipe, there are just three script classes, and most of the work is in setting up the cube wall and red sphere projectile, as seen in the following screenshot:
In the 0423_09_05
folder, you'll find a stone image texture file called stones.png
shown as follows:
To fire a projectile, perform the following steps:
BrickCube
, sized (1, 1, 1). Apply the image texture stones.png
, give it the tag brick
, and add a Rigidbody component from Physics to this GameObject.Prefab-brick
, and drag BrickCube into it.Prefab-brick
:// file: DestroyWhenFall.cs using UnityEngine; using System.Collections; public class DestroyWhenFall : MonoBehaviour { private const float MIN_Y = -1; void Update () { float y = transform.position.y; if( y < MIN_Y ) Destroy( gameObject); } }
BrickPrefab
in the scene at the following positions:SphereRed
, sized (1, 1, 1) with a red material, and add a Rigidbody component from Physics to this GameObject.Prefab-sphere
, and drag SphereRed into the Prefab.DestroyWhenFall
script class to Prefab-sphere
.// file: GameOverManager.cs using UnityEngine; using System.Collections; public class GameOverManager : MonoBehaviour { private bool gameWon = false; void Update() { GameObject[] wallObjects = GameObject.FindGameObjectsWithTag("brick"); int numWallObjects = wallObjects.Length; if( numWallObjects < 1 ) gameWon = true; } void OnGUI() { if( gameWon ) GUILayout.Label("Well Done - you have destroyed the whole wall!"); } }
// file: FireProjectile.cs using UnityEngine; using System.Collections; public class FireProjectile : MonoBehaviour { public Rigidbody projectilePrefab; private const float MIN_Y = -1; private float projectileSpeed = 15f; /** shortest time between firing */ public const float FIRE_DELAY = 0.25f; private float nextFireTime = 0f; void Update() { if( Time.time > nextFireTime ) CheckFireKey(); } void CheckFireKey() { if( Input.GetButton("Fire1")) { CreateProjectile(); nextFireTime = Time.time + FIRE_DELAY; } } void CreateProjectile() { Rigidbody projectile = (Rigidbody)Instantiate(projectilePrefab, transform.position, transform.rotation); // create and apply velocity Vector3 projectileVelocity = (projectileSpeed * Vector3.forward); projectile.velocity = transform.TransformDirection( projectileVelocity ); } }
Both Prefabs (Prefab-sphere
and Prefab-brick
) contain rigid body components; this allows the physics engine to control instances of such objects. We can apply forces to objects with rigid bodies (for example, we can "throw" this object in a given direction), and the object will collide with and bounce off other objects. Both Prefabs also have an instance of the script class DestroyWhenFall
—this adds the simple behavior that when either the red sphere or brick objects fall below the level of the terrain (Y=0), the objects will be destroyed. This prevents the scene from becoming filled up with all of the old sphere projectiles that have been fired. Also, it allows us to detect when the game is completed, that is, when all of the brick objects have been pushed off the terrain. Testing this condition is the responsibility of the GameOverManager
scripted component of the Main Camera from First Person Controller. For each frame it counts the number of objects tagged with brick
, and once that number is zero, the game completed message is displayed.
At the heart of this mini-game is the Main Camera scripted component FireProjectile
of First Person Controller. Variable nextFireTime
stores the time when the next projectile may be fired, and for each frame the current time is tested to see if that time has been reached. If it has, then the CheckFireKey()
method is called. This variable is initialized to zero, so the user doesn't have to wait at all to fire the first time.
The CheckFireKey()
method tests whether the user is pressing the Fire button at that instant. If the Fire button is pressed, the CreateProjectile()
method is called, and the next time duration the player can fire is set to FIRE_DELAY
seconds in the future.
The CreateProjectile()
method creates a new instance of the SpherePrefab
(via the public variable Prefab-projectile
) at the same position and rotation of the Main Camera of First Person Controller. Note that as the camera scripted component FireProjectile
is attached to this camera, any references to transform will retrieve the transform component of that camera.
A velocity vector for the force (movement) to be applied to the sphere projectile is calculated by multiplying the forward vector with the projectileSpeed
variable—this variable can be tweaked to achieve the speed of firing desired for a game. To ensure the sphere projectile is moved in the same direction the camera is facing, the TransformDirection()
method
of the camera's transform component is used to set the projectile's velocity property. The result is that a new sphere projectile instance is created at the camera location, and the force is applied to make it be fired in the direction the user has oriented the camera of First Person Controller.
The following are some details you don't want to miss.
For small games, where efficiency isn't an issue, the use of FindGameObjectsWithTag()
is fine. However, when tweaking a complex game to improve efficiency, the use of this method should be avoided as it requires a search of all GameObjects in a scene and needs to test their tag names again. A more efficient approach would be to maintain a count of the number of instances that have been created, and then to ensure that this count (list) is updated each time an object is destroyed. Thus, the count (or size of the list) is all that would be needed to be tested against for our game over condition. Another alternative to FindGameObjectsWithTag()
is to maintain a dynamic list of all object references. See Chapter 10, Improving Games with Extra Features and Optimization, for such optimization recipes.
3.144.39.144