Unity Game Development: Interactions (Part 2)

Exclusive offer: get 50% off this eBook here
Unity Game Development Essentials

Unity Game Development Essentials — Save 50%

Build fully functional, professional 3D games with realistic environments, sound, dynamic effects, and more!

£18.99    £9.50
by Will Goldstone | October 2009 |

In the previous part of the article by Will Goldstone, we saw how to add an outpost model to our project and also learned how to position, scale, assign colliders to objects as well as tag objects. In this part, we will look at the two differing approaches for triggering the animation giving you an overview of the two techniques that will both become useful in many other game development situations. In the first approach, we'll use collision detection—a crucial concept to get to grips with as you begin to work on games in Unity. In the second approach, we'll implement a simple ray cast forward from the player.

Opening the outpost

In this section, we will look at the two differing approaches for triggering the animation giving you an overview of the two techniques that will both become useful in many other game development situations. In the first approach, we'll use collision detection—a crucial concept to get to grips with as you begin to work on games in Unity. In the second approach, we'll implement a simple ray cast forward from the player.

Approach 1—Collision detection

To begin writing the script that will trigger the door-opening animation and thereby grant access to the outpost, we need to consider which object to write a script for.

In game development, it is often more efficient to write a single script for an object that will interact with many other objects, rather than writing many individual scripts that check for a single object. With this in mind, when writing scripts for a game such as this, we will write a script to be applied to the player character in order to check for collisions with many objects in our environment, rather than a script made for each object the player may interact with, which checks for the player.

Creating new assets

Before we introduce any new kind of asset into our project, it is good practice to create a folder in which we will keep assets of that type. In the Project panel, click on the Create button, and choose Folder from the drop-down menu that appears.

Rename this folder Scripts by selecting it and pressing Return (Mac) or by pressing F2 (PC).

Next, create a new JavaScript file within this folder simply by leaving the Scripts folder selected and clicking on the Project panel's Create button again, this time choosing JavaScript.

By selecting the folder, you want a newly created asset to be in before you create them, you will not have to create and then relocate your asset, as the new asset will be made within the selected folder.

Rename the newly created script from the default—NewBehaviourScript—to PlayerCollisions. JavaScript files have the file extension of .js but the Unity Project panel hides file extensions, so there is no need to attempt to add it when renaming your assets.

You can also spot the file type of a script by looking at its icon in the Project panel. JavaScript files have a 'JS' written on them, C# files simply have 'C#' and Boo files have an image of a Pacman ghost, a nice little informative pun from the guys at Unity Technologies!

Scripting for character collision detection

To start editing the script, double-click on its icon in the Project panel to launch it in the script editor for your platform—Unitron on Mac, or Uniscite on PC.

Working with OnControllerColliderHit

By default, all new JavaScripts include the Update() function, and this is why you'll find it present when you open the script for the first time. Let's kick off by declaring variables we can utilise throughout the script.

Our script begins with the definition of four variables, public member variables and two private variables. Their purposes are as follows:

  • doorIsOpen: a private true/false (boolean) type variable acting as a switch for the script to check if the door is currently open.
  • doorTimer: a private floating-point (decimal-placed) number variable, which is used as a timer so that once our door is open, the script can count a defined amount of time before self-closing the door.
  • currentDoor: a private GameObject storing variable used to store the specific currently opened door. Should you wish to add more than one outpost to the game at a later date, then this will ensure that opening one of the doors does not open them all, which it does by remembering the most recent door hit.
  • doorOpenTime: a floating-point (potentially decimal) numeric public member variable, which will be used to allow us to set the amount of time we wish the door to stay open in the Inspector.
  • doorOpenSound/doorShutSound: Two public member variables of data type AudioClip, for allowing sound clip drag-and-drop assignment in the Inspector panel.

Define the variables above by writing the following at the top of the PlayerCollisions script you are editing:

private var doorIsOpen : boolean = false;
private var doorTimer : float = 0.0;
private var currentDoor : GameObject;

var doorOpenTime : float = 3.0;
var doorOpenSound : AudioClip;
var doorShutSound : AudioClip;

Next, we'll leave the Update() function briefly while we establish the collision detection function itself. Move down two lines from:

function Update(){
}

And write in the following function:

function OnControllerColliderHit(hit : ControllerColliderHit){
}

This establishes a new function called OnControllerColliderHit. This collision detection function is specifically for use with player characters such as ours, which use the CharacterController component. Its only parameter hit is a variable that stores information on any collision that occurs. By addressing the hit variable, we can query information on the collision, including—for starters—the specific game object our player has collided with.

We will do this by adding an if statement to our function. So within the function's braces, add the following if statement:

function OnControllerColliderHit(hit: ControllerColliderHit){
if(hit.gameObject.tag == "outpostDoor" && doorIsOpen == false){
}
}

In this if statement, we are checking two conditions, firstly that the object we hit is tagged with the tag outpostDoor and secondly that the variable doorOpen is currently set to false. Remember here that two equals symbols (==) are used as a comparative, and the two ampersand symbols (&&) simply say 'and also'. The end result means that if we hit the door's collider that we have tagged and if we have not already opened the door, then it may carry out a set of instructions.

We have utilized the dot syntax to address the object we are checking for collisions with by narrowing down from hit (our variable storing information on collisions) to gameObject (the object hit) to the tag on that object.

If this if statement is valid, then we need to carry out a set of instructions to open the door. This will involve playing a sound, playing one of the animation clips on the model, and setting our boolean variable doorOpen to true. As we are to call multiple instructions—and may need to call these instructions as a result of a different condition later when we implement the ray casting approach—we will place them into our own custom function called OpenDoor.

We will write this function shortly, but first, we'll call the function in the if statement we have, by adding:

OpenDoor();

So your full collision function should now look like this:

function OnControllerColliderHit(hit: ControllerColliderHit){
if(hit.gameObject.tag == "outpostDoor" && doorIsOpen == false){
OpenDoor();
}
}

Writing custom functions

Storing sets of instructions you may wish to call at any time should be done by writing your own functions. Instead of having to write out a set of instructions or "commands" many times within a script, writing your own functions containing the instructions means that you can simply call that function at any time to run that set of instructions again. This also makes tracking mistakes in code—known as Debugging—a lot simpler, as there are fewer places to check for errors.

In our collision detection function, we have written a call to a function named OpenDoor. The brackets after OpenDoor are used to store parameters we may wish to send to the function—using a function's brackets, you may set additional behavior to pass to the instructions inside the function. We'll take a look at this in more depth later in this article under the heading Function Efficiency. Our brackets are empty here, as we do not wish to pass any behavior to the function yet.

Declaring the function

To write the function we need to call, we simply begin by writing:

function OpenDoor(){
}

In between the braces of the function, much in the same way as the instructions of an if statement, we place any instructions to be carried out when this function is called.

Playing audio

Our first instruction is to play the audio clip assigned to the variable called doorOpenSound. To do this, add the following line to your function by placing it within the curly braces after { "and before" }:

audio.PlayOneShot(doorOpenSound);

To be certain, it should look like this:

function OpenDoor(){
audio.PlayOneShot(doorOpenSound);
}

Here we are addressing the Audio Source component attached to the game object this script is applied to (our player character object, First Person Controller), and as such, we'll need to ensure later that we have this component attached; otherwise, this command will cause an error.

Addressing the audio source using the term audio gives us access to four functions, Play(), Stop(), Pause(), and PlayOneShot(). We are using PlayOneShot because it is the best way to play a single instance of a sound, as opposed to playing a sound and then switching clips, which would be more appropriate for continuous music than sound effects. In the brackets of the PlayOneShot command, we pass the variable doorOpenSound, which will cause whatever sound file is assigned to that variable in the Inspector to play. We will download and assign this and the clip for closing the door after writing the script.

Checking door status

One condition of our if statement within our collision detection function was that our boolean variable doorIsOpen must be set to false. As a result, the second command inside our OpenDoor() function is to set this variable to true.

This is because the player character may collide with the door several times when bumping into it, and without this boolean, they could potentially trigger the OpenDoor() function many times, causing sound and animation to recur and restart with each collision. By adding in a variable that when false allows the OpenDoor() function to run and then disallows it by setting the doorIsOpen variable to true immediately, any further collisions will not re-trigger the OpenDoor() function.

Add the line:

doorOpen = true;

to your OpenDoor() function now by placing it between the curly braces after the previous command you just added.

Playing animation

We have already imported the outpost asset package and looked at various settings on the asset before introducing it to the game in this article. One of the tasks performed in the import process was the setting up of animation clips using the Inspector. By selecting the asset in the Project panel, we specified in the Inspector that it would feature three clips:

  • idle (a 'do nothing' state)
  • dooropen
  • doorshut

In our openDoor() function, we'll call upon a named clip using a String of text to refer to it. However, first we'll need to state which object in our scene contains the animation we wish to play. Because the script we are writing is to be attached to the player, we must refer to another object before referring to the animation component. We do this by stating the line:

var myOutpost : GameObject = GameObject.Find("outpost");

Here we are declaring a new variable called myOutpost by setting its type to be a GameObject and then selecting a game object with the name outpost by using GameObject.Find. The Find command selects an object in the current scene by its name in the Hierarchy and can be used as an alternative to using tags.

Now that we have a variable representing our outpost game object, we can use this variable with dot syntax to call animation attached to it by stating:

myOutpost.animation.Play("dooropen");

This simply finds the animation component attached to the outpost object and plays the animation called dooropen. The play() command can be passed any string of text characters, but this will only work if the animation clips have been set up on the object in question.

Your finished OpenDoor() custom function should now look like this:

function OpenDoor(){
audio.PlayOneShot(doorOpenSound);
doorIsOpen = true;
var myOutpost : GameObject = GameObject.Find("outpost");
myOutpost.animation.Play("dooropen");
}
Reversing the procedure

Now that we have created a set of instructions that will open the door, how will we close it once it is open? To aid playability, we will not force the player to actively close the door but instead establish some code that will cause it to shut after a defined time period.

This is where our doorTimer variable comes into play. We will begin counting as soon as the door becomes open by adding a value of time to this variable, and then check when this variable has reached a particular value by using an if statement.

Because we will be dealing with time, we need to utilize a function that will constantly update such as the Update() function we had awaiting us when we created the script earlier.

Create some empty lines inside the Update() function by moving its closing curly brace } a few lines down.

Firstly, we should check if the door has been opened, as there is no point in incrementing our timer variable if the door is not currently open. Write in the following if statement to increment the timer variable with time if the doorIsOpen variable is set to true:

if(doorIsOpen){
doorTimer += Time.deltaTime;
}

Here we check if the door is open — this is a variable that by default is set to false, and will only become true as a result of a collision between the player object and the door. If the doorIsOpen variable is true, then we add the value of Time.deltaTime to the doorTimer variable. Bear in mind that simply writing the variable name as we have done in our if statement's condition is the same as writing doorIsOpen == true.

Time.deltaTime is a Time class that will run independent of the game's frame rate. This is important because your game may be run on varying hardware when deployed, and it would be odd if time slowed down on slower computers and was faster when better computers ran it. As a result, when adding time, we can use Time.deltaTime to calculate the time taken to complete the last frame and with this information, we can automatically correct real-time counting.

Next, we need to check whether our timer variable, doorTimer, has reached a certain value, which means that a certain amount of time has passed. We will do this by nesting an if statement inside the one we just added—this will mean that the if statement we are about to add will only be checked if the doorIsOpen if condition is valid.

Add the following code below the time incrementing line inside the existing if statement:

if(doorTimer > doorOpenTime){
shutDoor();
doorTimer = 0.0;
}

This addition to our code will be constantly checked as soon as the doorIsOpen variable becomes true and waits until the value of doorTimer exceeds the value of the doorOpenTime variable, which, because we are using Time.deltaTime as an incremental value, will mean three real-time seconds have passed. This is of course unless you change the value of this variable from its default of 3 in the Inspector.

Once the doorTimer has exceeded a value of 3, a function called shutDoor() is called, and the doorTimer variable is reset to zero so that it can be used again the next time the door is triggered. If this is not included, then the doorTimer will get stuck above a value of 3, and as soon as the door was opened it would close as a result.

Your completed Update() function should now look like this:

function Update(){
if(doorIsOpen){
doorTimer += Time.deltaTime;

if(doorTimer > 3){
shutDoor();
doorTimer = 0.0;
}
}
}

Now, add the following function called shutDoor() to the bottom of your script. Because it performs largely the same function as openDoor(), we will not discuss it in depth. Simply observe that a different animation is called on the outpost and that our doorIsOpen variable gets reset to false so that the entire procedure may start over:

function shutDoor(){
audio.PlayOneShot(doorShutSound);
doorIsOpen = false;

var myOutpost : GameObject = GameObject.Find("outpost");
myOutpost.animation.Play("doorshut");
}

Unity Game Development Essentials Build fully functional, professional 3D games with realistic environments, sound, dynamic effects, and more!
Published: October 2009
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:
Function efficiency

Now that we have a script in charge of opening and closing our door, let's look at how we can expand our knowledge of bespoke functions to make our scripting more efficient.

Currently we have two functions we refer to as custom or bespoke—openDoor() and shutDoor(). These functions perform the same three tasks—they play a sound, set a boolean variable, and play an animation. So why not create a single function and add parameters to allow it to play differing sounds and have it choose either true or false for the boolean and play differing animations? Making these three tasks into parameters of the function will allow us to do just that. Parameters are settings specified in the brackets of the function. They must be separated by commas and should be given a specified type when declared in the function.

At the bottom of your script, add the following function:

function Door(aClip : AudioClip, openCheck : boolean, animName : 
String, thisDoor : GameObject){
audio.PlayOneShot(aClip);
doorIsOpen = openCheck;

thisDoor.transform.parent.animation.Play(animName);
}

You'll notice that this function looks similar to our existing open and shut functions, but has four parameters in its declaration—aClip, openCheck, animName, and thisDoor. These are effectively variables that get assigned when the function is called, and the value assigned to them is used inside the function. For example, when we wish to pass values for opening the door to this function, we would call the function and set each parameter by writing:

Door(doorOpenSound, true, "dooropen", currentDoor);

This feeds the variable doorOpenSound to the aClip parameter, a value of true to the openCheck parameter, string of text "dooropen" to the animName parameter, and sends the variable currentDoor to the thisDoor parameter.

Now we can replace the call to the openDoor() function inside the collision detection function. However, we are yet to set the currentDoor variable, so we will need to do this as well. First, remove the following line that calls the OpenDoor() function inside the OnControllerColliderHit() function:

OpenDoor();

Replace it with the following two lines:

currentDoor = hit.gameObject;
Door(doorOpenSound, true, "dooropen", currentDoor);

Here we are setting the currentDoor variable to whichever object has been most recently collided with. This allows us to then pass this information to our function, ensuring that we only open—that is trigger animation on—the specific outpost we're colliding with, rather than any outpost with a tagged door.

In the last line of this function, we play the correct animation by using thisDoor.transform.parent.animation—by using the dot syntax here, we are tracing steps back to the animation component we need to address. The thisDoor variable has been fed the object most recently hit—stored in the currentDoor variable—and we then address the door's parent object—the outpost itself, as it is this that has the animation component attached to it, not the door. For example, we could not say:

thisDoor.animation.Play(animName);

Unity would tell us that there is no animation component. So, instead, we address the door child object's transform parent—the object it belongs to in the Hierarchy—and then simply select the animation component from there.

Finally, because we are using this new method of opening and closing the doors, we'll need to amend the door closing code within the Update() function. Within the if statement that checks for the doorTimer variable exceeding the value of the doorOpenTime variable, replace the call to the shutDoor() function with this line:

Door(doorShutSound, false, "doorshut", currentDoor);

You may now delete the original two functions—openDoor() and shutDoor() as our customizable Door() function now supersedes both of them. By creating functions in this way, we are not repeating ourselves in scripting, and this makes our script more efficient and saves time writing two functions.

Finishing the script

To complete the script, we will make sure that the object it gets added to has an AudioSource component—this is needed to play back sound clips as we do in our Door function.

Add the following line to the very bottom of the script, ensuring that the line is NOT terminated with a semi-colon as you'd expect. This is because this command is Unity-specific, and not part of the JavaScript calls expected.

@script RequireComponent(AudioSource)

Attaching the script

Save your script in the script editor to ensure that the Unity editor receives any updates you have made—this should be done with any changes you make to your scripts; otherwise Unity cannot re-compile the script.

Next, switch back to Unity. Check the bottom bar of the Unity interface, as this is where any errors made in the script will be shown. If there are any errors, then double-click on the error and ensure that your script matches that written above. As you continue to work with scripting in Unity, you'll get used to using the error reporting to help you correct any mistakes you may make. The best place to begin double-checking that you have not made any mistakes in your code is to ensure that you have an even number of opening and closing curly braces—this means that all functions and if statements are correctly closed.

If you have no errors, then simply select the object you wish to apply the script to in the Hierarchy—the First Person Controller object.

There are two methods of attaching a script to any object in Unity. Firstly, you may drag-and-drop the script itself from the Project panel onto the object in the Hierarchy or Scene views, or simply select the object you wish to apply the script to (as you just did), and from the top menu, go to Component | Scripts | Player Collisions. The Scripts sub-menu of the Component menu simply lists any scripts it finds in the current Project; so as soon as your script is created and saved, it will be available to choose from that menu.

The Inspector panel for the First Person Controller should now show Player Collisions Script as a component along with an Audio Source component that automatically was added as a result of using the RequireComponent command at the end of our script.

Unity Game Development: Interactions (Part 2)

You should note that our public member variables, doorOpenTime, doorOpenSound, and doorShutSound appear in the Inspector so that you may adjust the value of doorOpenTime and drag-and-drop sound files from the Project panel to assign to the two sound variables. This is true of any member variables you may encounter while writing scripts—remember you can hide public member variables from appearing in this manner simply by using the prefix private, as we have done with the other three variables used in the script. Bear in mind that when using private variables, they should be assigned a type or value in the script to avoid errors, otherwise they will be "null"—have no value.

The Door Open and Door Close sound clips are available in the code bundle provided on packtpub.com (www.packtpub.com/files/code/8181_Code.zip). Extract the files and locate the package named doorSounds.unitypackage. To import this package, return to Unity and go to Assets | Import Package, navigate to the file you have downloaded and select it. The Import Assets dialog window will appear listing the two files in the package. Simply click on the Import button on this window to confirm the import. You should now have doorSlideOpen and doorSlideShut audio clips in your Project panel's list of assets, as shown in the following screenshot. To keep things neat, drag them both into the existing Sounds folder, where we already have our hillside ambient audio clip.

Unity Game Development: Interactions (Part 2)

Now that the assets are in your project folder, drag-and-drop the appropriate clip to the public member variables in the Inspector for the Player Collisions (Script) component. Once this is done, the script component should look like this:

Unity Game Development: Interactions (Part 2)

The collision between the player and the door will be detected, causing the door to open, and after 3 seconds the door will close automatically. Press the Play button again to finish testing.

To allow the door to open without being pressed against it, we will extend the collider of the door, as discussed at the beginning of the article. Expand the child objects beneath the outpost game object in the Hierarchy panel by clicking on the gray arrow next to the object, and then select the child object named door. Now with your mouse cursor over the Scene view, press F to focus the view on that object:

Unity Game Development: Interactions (Part 2)

In the Inspector for this object, expand the Size parameter of the Box Collider component, so that you can adjust the Z value (depth) of the collider itself. Change the existing value to a value of 1.5, so that the collider becomes extended, as shown in the following screenshot:

Unity Game Development: Interactions (Part 2)

Now try playing the test again, and you will notice that the door opens sooner upon approaching it, as the boundary of the collider extends further from the visible door.

However, you should also note the physical bumping into the collider that occurs. This can be countered by utilizing the Trigger mode of the collider. So for now, we will implement the second approach discussed earlier—ray casting. By casting a ray forward from the player, we will negate the need for our player character's collider to actually touch the door's collider.

Approach 2-Ray casting

In this section, we will implement an alternate approach to opening the door with collision detection. Although collision detection or trigger detection would be a valid approach, by introducing the concept of ray casting, we can ensure that our player only opens the door of the outpost when they are facing it, because the ray will always face the direction the First Person Controller is facing, and as such not intersect the door if, for example, the player backs up to it.

Disabling collision detection—using comments

To avoid the need to write an additional script, we will simply comment out—that is, temporarily deactivate part of the code that contains our collision detection function. To do this, we will add characters to turn our working collision code into a comment.

In programming, comments can be left as a reminder but are never executed. So to deactivate a piece of code, you can turn it into a comment—we refer to this as commenting out.

Switch back to your script editor (Unitron/Uniscite) and before the line:

function OnControllerColliderHit(hit: ControllerColliderHit){

place the following characters:

/*

Putting a forward slash and asterisk into your script begins a mass comment (as opposed to two forward slashes that simply comment out a single line). After the collision detection function, place the reverse of this, that is, an asterisk followed by a forward slash. Your entire function should have changed the syntax color in the script editor and be written like this:

/*
function OnControllerColliderHit(hit: ControllerColliderHit){
if(hit.gameObject.tag == "outpostDoor" && doorIsOpen == false){
currentDoor = hit.gameObject;
Door(doorOpenSound, true, "dooropen", currentDoor);
}
}
*/

Resetting the door collider

As we do not want the effect of bumping into the invisible collider on the door, return to the Box Collider component of the door by selecting the door child object in the Inspector and then setting the Size value of the Z axis to 0.1. This will match the door's depth visually.

Adding the ray

Switch back to the script editor and move your cursor a couple of lines down from the opening of the Update() function. We place ray casts in the Update() function, as we need to technically cast our ray forward every frame. Add in the following code:

var hit : RaycastHit;
if(Physics.Raycast (transform.position,
transform.forward, hit, 5)) {
if(hit.collider.gameObject.tag=="outpostDoor"
&& doorIsOpen == false){
currentDoor = hit.collider.gameObject;
Door(doorOpenSound, true, "dooropen", currentDoor);
}
}

At the outset, a ray is created by establishing a private variable called hit, which is of type RaycastHit. Note that it does not need a private prefix to not be seen in the Inspector—it is simply private by default because it is declared inside a function. This will be used to store information on the ray when it intersects colliders. Whenever we refer to the ray, we use this variable.

Then we use two if statements. The parent if is in charge of casting the ray and uses the variable we created. Because we place the casting of the ray into an if statement, we are able to only call the nested if statement if the ray hits an object, making the script more efficient.

Our first if contains Physics.Raycast, the actual command that casts the ray. This command has four parameters within its own brackets:

  • The position from which to create the ray (transform.position—the position of the object this script applies to, that is First Person Controller)
  • The direction of the ray (transform.forward—the forward direction of the object this script applies to)
  • The RaycastHit data structure we set up called hit—the ray stored as a variable
  • The length of the ray (5—a distance in the game units, meters)

Then we have a nested if statement that first checks the hit variable for collision with colliders in the game world, specifically whether we have hit a collider belonging to a game object tagged outpostDoor, so:

hit.collider.gameObject.tag

Secondly, this if statement ensures—as our collision detection function did—that the doorIsOpen boolean variable is false. Again, this is to ensure that the door will not re-trigger many times once it has started to open.

Once both if statements' conditions are met, we simply set the currentDoor variable to the object stored in hit and then call the Door() function in the same way that we did in our collision detection function:

currentDoor = hit.collider.gameObject;
Door(doorOpenSound, true, "dooropen");

Playtest the game once more and you will notice that when approaching the door, it not only opens before you bump into it, but also only opens when you are facing it, because the ray that detects the door is cast in the same direction that the player character faces.

Summary

In this article, we have explored two key methods for detecting interactions between objects in 3D games. Both ray casting and collision detection have many individual uses, and they are key skills that you should expect to reuse in your future use of Unity.

Unity Game Development Essentials Build fully functional, professional 3D games with realistic environments, sound, dynamic effects, and more!
Published: October 2009
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

About the Author :


Will Goldstone

Will Goldstone is a longstanding member of the Unity community and works for Unity Technologies as a Technical Support Associate, handling educational content and developer support. With an MA in Creative Education, and many years experience as a lecturer in higher education, Will wrote the first ever Unity book, the original Unity Game Development Essentials, and also created the first ever video tutorials for the package. Through his sites http://www.unity3dstudent.com and http://learnunity3d.com Will helps to introduce new users to the growing community of developers discovering Unity every day.

Books From Packt

Flash with Drupal
Blender 3D Architecture, Buildings, and Scenery

Blender 3D 2.49 Incredible Machines
Blender 3D 2.49 Incredible Machines

Papervision3D Essentials
Papervision3D Essentials

Drupal 6 Search Engine Optimization
Drupal 6 Search Engine Optimization

3D Game Development with Microsoft Silverlight 3: Beginner's Guide
3D Game Development with Microsoft Silverlight 3: Beginner's Guide

Drupal Multimedia
Drupal Multimedia

Moodle 1.9 Multimedia
Moodle 1.9 Multimedia

Flash with Drupal
Flash with Drupal

 

 

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software