Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials - 3D Game Development

115 Articles
article-image-getting-started
Packt
26 Dec 2012
6 min read
Save for later

Getting Started

Packt
26 Dec 2012
6 min read
(For more resources related to this topic, see here.) System requirements Before we take a look at how to download and install ShiVa3D, it might be a good idea to see if your system will handle it. The minimum requirements for the ShiVa3D editor are as follows: Microsoft Windows XP and above, Mac OS with Parallels Intel Pentium IV 2 GHz or AMD Athlon XP 2600+ 512 MB of RAM 3D accelerated graphics card with 64 MB RAM and 1440 x 900 resolution Network interface In addition to the minimum requirements, the following suggestions will give you the best gaming experience: Intel Core Duo 1.8 GHz or AMD Athlon 64 X2 3600+ 1024 MB of RAM Modern 3D accelerated graphics card with 256 MB RAM and 1680 x 1050 resolution Sound card Downloading ShiVa3D Head over to http://www.stonetrip.com and get a copy of ShiVa3D Web Edition. Currently, there is a download link on the home page. Once you get to the Download page, enter your email address and click on the Download button. If everything goes right, you will be prompted for a save location—save it in a place that will be easy to find later. That's it for the download, but you may want to take a second to look around Stonetrip's website. There are links to the documentation, forum, wiki, and news updates. It will be well worth your time to become familiar with the site now since you will be using it frequently. Installing ShiVa3D Assuming your computer meets the minimum requirements, installation should be pretty easy. Simply find the installation file that you downloaded and run it. I recommend sticking with the default settings. If you do have issues getting it installed, it is most likely due to a technical problem, so head on over to the forums, and we will be more than glad to lend a helping hand. The ShiVa editor Several different applications were installed, if you accepted the default installation choices. The only one we are going to worry about for most of this book is the ShiVa Web Edition editor, so go ahead and open it now. By default, ShiVa opens with a project named Samples loaded. You can tell by looking at the lower right-hand quadrant of the screen in the Data Explorer—the root folder is named Samples, as shown in the following screenshot: This is actually a nice place to start, because there are all sorts of samples that we can play with. We'll come back to those once we have had a chance to make our own game. We will cover the editor in more detail later, but for now it is important to notice that the default layout has four sections: Attributes Editor, Game Editor, Scene Viewer, and Data Explorer. Each of these sections represents a module within the editor. The Data Explorer window, for example, gives us access to all of the resources that can be used in our project such as materials, models, fonts, and so on. Creating a project A project is the way by which we can group games that share the same resources.To create a new project, click on Main | Projects in the upper left-hand corner of the screen. The project window will open, as shown in the following screenshot: In this window, we can see the Samples project along with its path. The green light next to the name indicates that Samples is the project currently loaded into the editor. If there were other projects listed, the other projects would have red lights besides their names. The steps for creating a new project are as follows: Click on the Add button to create a new project. Navigate to the location we want for our project and then right-click in the explorer area and select New | Folder. Name the folder as IntroToShiva, highlight the folder and click on Select. The project window will now show our new project has the green light and the Samples project has a red light. Click on the Close button to finish. Notice that the root folder in the Data Context window now says IntroToShiva. Creating a game Games are exactly what you would think they are and it's time we created ours. The steps for creating our own games are as follows: Go to the Game Editor window in the lower left-hand corner and click on Game | Create. A window will pop up asking for the game name.We will be creating a game in which the player must fly a spaceship through a tunnel or cave and avoid obstacles; so let's call the game CaveRunner. Click on the OK button and the bottom half of our editor should look like the following screenshot: Notice that there is now some information displayed in the Game Editor window and the Data Explorer window shows the CaveRunner game in the Games folder. A game is simply the empty husk of what we are really trying to build. Next, we will begin building out our game by adding a scene. Making a scene We can think of a scene as a level in a game—it is the stage upon which we place our objects, so that the player can interact with them. We can create a scene by performing the following steps: Click on Edit | Scene | Create in the Game Editor window. Name the scene as Level1 and click on the OK button. The new scene is created and opened for immediate use, as shown in the following screenshot: We can tell Level1 is open, because the Game Editor window switched to the Scenes tab and now Level1 has a green check mark next to it; we can also see a grid in the Scene Viewer window. Additionally, the scene information is displayed in the upper left-hand corner of the Scene Viewer window and the Scene tag says Level1. So we were able to get a scene created, but it is sadly empty—it's not much of a level in even the worst of games. If we want this game to be worth playing, we better add something interesting. Let's start by importing a ship.
Read more
  • 0
  • 0
  • 11511

article-image-collision-detection-and-physics-panda3d-game-development
Packt
30 Mar 2011
12 min read
Save for later

Collision Detection and Physics in Panda3D Game Development

Packt
30 Mar 2011
12 min read
Panda3D 1.7 Game Developer's Cookbook Over 80 recipes for developing 3D games with Panda3D, a full-scale 3D game engine In a video game, the game world or level defines the boundaries within which the player is allowed to interact with the game environment. But how do we enforce these boundaries? How do we keep the player from running through walls? This is where collision detection and response come into play. Collision detection and response not only allow us to keep players from passing through the level boundaries, but also are the basis for many forms of interaction. For example, lots of actions in games are started when the player hits an invisible collision mesh, called a trigger, which initiates a scripted sequence as a response to the player entering its boundaries. Simple collision detection and response form the basis for nearly all forms of interaction in video games. It’s responsible for keeping the player within the level, for crates being pushable, for telling if and where a bullet hit the enemy. What if we could add some extra magic to the mix to make our games even more believable, immersive, and entertaining? Let’s think again about pushing crates around: What happens if the player pushes a stack of crates? Do they just move like they have been glued together, or will they start to tumble and eventually topple over? This is where we add physics to the mix to make things more interesting, realistic, and dynamic. In this article, we will take a look at the various collision detection and physics libraries that the Panda3D engine allows us to work with. Putting in some extra effort, we will also see that it is not very hard to integrate a physics engine that is not part of the Panda3D SDK. Using the built-in collision detection system Not all problems concerning world and player interaction need to be handled by a fully fledged physics API—sometimes a much more basic and lightweight system is just enough for our purposes. This is why in this recipe we dive into the collision handling system that is built into the Panda3D engine. Getting ready This recipe relies upon the project structure created in Setting up the game structure (code download-Ch:1), Setting Up Panda3D and Configuring Development Tools. How to do it... Let’s go through this recipe’s tasks: Open Application.py and add the include statements as well as the constructor of the Application class: from direct.showbase.ShowBase import ShowBase from panda3d.core import * import random class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.cam.setPos(0, -50, 10) self.setupCD() self.addSmiley() self.addFloor() taskMgr.add(self.updateSmiley, "UpdateSmiley") Next, add the method that initializes the collision detection system: def setupCD(self): base.cTrav = CollisionTraverser() base.cTrav.showCollisions(render) self.notifier = CollisionHandlerEvent() self.notifier.addInPattern("%fn-in-%in") self.accept("frowney-in-floor", self.onCollision) Next, implement the method for adding the frowney model to the scene: def addSmiley(self): self.frowney = loader.loadModel("frowney") self.frowney.reparentTo(render) self.frowney.setPos(0, 0, 10) self.frowney.setPythonTag("velocity", 0) col = self.frowney.attachNewNode(CollisionNode("frowney")) col.node().addSolid(CollisionSphere(0, 0, 0, 1.1)) col.show() base.cTrav.addCollider(col, self.notifier) The following methods will add a floor plane to the scene and handle the collision response: def addFloor(self): floor = render.attachNewNode(CollisionNode("floor")) floor.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))) floor.show() def onCollision(self, entry): vel = random.uniform(0.01, 0.2) self.frowney.setPythonTag("velocity", vel) Add this last piece of code. This will make the frowney model bounce up and down: def updateSmiley(self, task): vel = self.frowney.getPythonTag("velocity") z = self.frowney.getZ() self.frowney.setZ(z + vel) vel -= 0.001 self.frowney.setPythonTag("velocity", vel) return task.cont Hit the F6 key to launch the program: How it works... We start off by adding some setup code that calls the other initialization routines. We also add the task that will update the smiley’s position. In the setupCD() method, we initialize the collision detection system. To be able to find out which scene objects collided and issue the appropriate responses, we create an instance of the CollisionTraverser class and assign it to base.cTrav. The variable name is important, because this way, Panda3D will automatically update the CollisionTraverser every frame. The engine checks if a CollisionTraverser was assigned to that variable and will automatically add the required tasks to Panda3D’s update loop. Additionally, we enable debug drawing, so collisions are being visualized at runtime. This will overlay a visualization of the collision meshes the collision detection system uses internally. In the last lines of setupCD(), we instantiate a collision handler that sends a message using Panda3D’s event system whenever a collision is detected. The method call addInPattern(“%fn-in-%in”) defines the pattern for the name of the event that is created when a collision is encountered the first time. %fn will be replaced by the name of the object that bumps into another object that goes by the name that will be inserted in the place of %in. Take a look at the event handler that is added below to get an idea of what these events will look like. After the code for setting up the collision detection system is ready, we add the addSmiley() method, where we first load the model and then create a new collision node, which we attach to the model’s node so it is moved around together with the model. We also add a sphere collision shape, defined by its local center coordinates and radius. This is the shape that defines the boundaries; the collision system will test against it to determine whether two objects have touched. To complete this step, we register our new collision node with the collision traverser and configure it to use the collision handler that sends events as a collision response. Next, we add an infinite floor plane and add the event handling method for reacting on collision notifications. Although the debug visualization shows us a limited rectangular area, this plane actually has an unlimited width and height. In our case, this means that at any given x- and y-coordinate, objects will register a collision when any point on their bounding volume reaches a z-coordinate of 0. It’s also important to note that the floor is not registered as a collider here. This is contrary to what we did for the frowney model and guarantees that the model will act as the collider, and the floor will be treated as the collidee when a contact between the two is encountered. While the onCollision() method makes the smiley model go up again, the code in updateSmiley() constantly drags it downwards. Setting the velocity tag on the frowney model to a positive or negative value, respectively, does this in these two methods. We can think of that as forces being applied. Whenever we encounter a collision with the ground plane, we add a one-shot bounce to our model. But what goes up must come down, eventually. Therefore, we continuously add a gravity force by decreasing the model’s velocity every frame. There’s more... This sample only touched a few of the features of Panda3D’s collision system. The following sections are meant as an overview to give you an impression of what else is possible. For more details, take a look into Panda3D’s API reference. Collision Shapes In the sample code, we used CollisionPlane and CollisionSphere, but there are several more shapes available: CollisionBox: A simple rectangular shape. Crates, boxes, and walls are example usages for this kind of collision shape. CollisionTube: A cylinder with rounded ends. This type of collision mesh is often used as a bounding volume for first and third person game characters. CollisionInvSphere: This shape can be thought of as a bubble that contains objects, like a fish bowl. Everything that is outside the bubble is reported to be colliding. A CollisionInvSphere may be used to delimit the boundaries of a game world, for example. CollisionPolygon: This collision shape is formed from a set of vertices, and allows for the creating of freeform collision meshes. This kind of shape is the most complex to test for collisions, but also the most accurate one. Whenever polygon-level collision detection is important, when doing hit detection in a shooter for example, this collision mesh comes in handy. CollisionRay: This is a line that, starting from one point, extends to infinity in a given direction. Rays are usually shot into a scene to determine whether one or more objects intersect with them. This can be used for various tasks like finding out if a bullet shot in the given direction hit a target, or simple AI tasks like finding out whether a bot is approaching a wall. CollisionLine: Like CollisionRay, but stretches to infinity in both directions. CollisionSegment: This is a special form of ray that is limited by two end points. CollisionParabola: Another special type of ray that is bent. The flying curves of ballistic objects are commonly described as parabolas. Naturally, we would use this kind of ray to find collisions for bullets, for example. Collision Handlers Just like it is the case with collision shapes for this recipe, we only used CollisionHandlerEvent for our sample program, even though there are several more collision handler classes available: CollisionHandlerPusher: This collision handler automatically keeps the collider out of intersecting vertical geometry, like walls. CollisionHandlerFloor: Like CollisionHandlerPusher, but works in the horizontal plane. CollisionHandlerQueue: A very simple handler. All it does is add any intersecting objects to a list. PhysicsCollisionHandler: This collision handler should be used in connection with Panda3D’s built-in physics engine. Whenever a collision is found by this collision handler, the appropriate response is calculated by the simple physics engine that is built into the engine. Using the built-in physics system Panda3D has a built-in physics system that treats its entities as simple particles with masses to which forces may be applied. This physics system is a great amount simpler than a fully featured rigid body one. But it still is enough for cheaply, quickly, and easily creating some nice and simple physics effects. Getting ready To be prepared for this recipe, please first follow the steps found in Setting up the game structure (code download-Ch:1). Also, the collision detection system of Panda3D will be used, so reading up on it in Using the built-in collision detection system might be a good idea! How to do it... The following steps are required to work with Panda3D’s built-in physics system: Edit Application.py and add the required import statements as well as the constructor of the Application class: from direct.showbase.ShowBase import ShowBase from panda3d.core import * from panda3d.physics import * class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.cam.setPos(0, -50, 10) self.setupCD() self.setupPhysics() self.addSmiley() self.addFloor() Next, add the methods for initializing the collision detection and physics systems to the Application class: def setupCD(self): base.cTrav = CollisionTraverser() base.cTrav.showCollisions(render) self.notifier = CollisionHandlerEvent() self.notifier.addInPattern("%fn-in-%in") self.notifier.addOutPattern("%fn-out-%in") self.accept("smiley-in-floor", self.onCollisionStart) self.accept("smiley-out-floor", self.onCollisionEnd) def setupPhysics(self): base.enableParticles() gravNode = ForceNode("gravity") render.attachNewNode(gravNode) gravityForce = LinearVectorForce(0, 0, -9.81) gravNode.addForce(gravityForce) base.physicsMgr.addLinearForce(gravityForce) Next, implement the method for adding a model and physics actor to the scene: def addSmiley(self): actor = ActorNode("physics") actor.getPhysicsObject().setMass(10) self.phys = render.attachNewNode(actor) base.physicsMgr.attachPhysicalNode(actor) self.smiley = loader.loadModel("smiley") self.smiley.reparentTo(self.phys) self.phys.setPos(0, 0, 10) thrustNode = ForceNode("thrust") self.phys.attachNewNode(thrustNode) self.thrustForce = LinearVectorForce(0, 0, 400) self.thrustForce.setMassDependent(1) thrustNode.addForce(self.thrustForce) col = self.smiley.attachNewNode(CollisionNode("smiley")) col.node().addSolid(CollisionSphere(0, 0, 0, 1.1)) col.show() base.cTrav.addCollider(col, self.notifier) Add this last piece of source code that adds the floor plane to the scene to Application.py: Application.py: def addFloor(self): floor = render.attachNewNode(CollisionNode("floor")) floor.node().addSolid(CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))) floor.show() def onCollisionStart(self, entry): base.physicsMgr.addLinearForce(self.thrustForce) def onCollisionEnd(self, entry): base.physicsMgr.removeLinearForce(self.thrustForce) Start the program by pressing F6: How it works... After adding the mandatory libraries and initialization code, we proceed to the code that sets up the collision detection system. Here we register event handlers for when the smiley starts or stops colliding with the floor. The calls involved in setupCD() are very similar to the ones used in Using the built-in collision detection system. Instead of moving the smiley model in our own update task, we use the built-in physics system to calculate new object positions based on the forces applied to them. In setupPhysics(), we call base.enableParticles() to fire up the physics system. We also attach a new ForceNode to the scene graph, so all physics objects will be affected by the gravity force. We also register the force with base.physicsMgr, which is automatically defined when the physics engine is initialized and ready. In the first couple of lines in addSmiley(), we create a new ActorNode, give it a mass, attach it to the scene graph and register it with the physics manager class. The graphical representation, which is the smiley model in this case, is then added to the physics node as a child so it will be moved automatically as the physics system updates. We also add a ForceNode to the physics actor. This acts as a thruster that applies a force that pushes the smiley upwards whenever it intersects the floor. As opposed to the gravity force, the thruster force is set to be mass dependant. This means that no matter how heavy we set the smiley to be, it will always be accelerated at the same rate by the gravity force. The thruster force, on the other hand, would need to be more powerful if we increased the mass of the smiley. The last step when adding a smiley is adding its collision node and shape, which leads us to the last methods added in this recipe, where we add the floor plane and define that the thruster should be enabled when the collision starts, and disabled when the objects’ contact phase ends.
Read more
  • 0
  • 0
  • 11173

article-image-skinning-character
Packt
21 Apr 2014
6 min read
Save for later

Skinning a character

Packt
21 Apr 2014
6 min read
(For more resources related to this topic, see here.) Our world in 5000 AD is incomplete without our mutated human being Mr. Green. Our Mr. Green is a rigged model, exported from Blender. All famous 3D games from Counter Strike to World of Warcraft use skinned models to give the most impressive real world model animations and kinematics. Hence, our learning has to now evolve to load Mr. Green and add the same quality of animation in our game. We will start our study of character animation by discussing the skeleton, which is the base of the character animation, upon which a body and its motion is built. Then, we will learn about skinning, how the bones of the skeleton are attached to the vertices, and then understand its animations. In this article, we will cover basics of a character's skeleton, basics of skinning, and some aspects of Loading a rigged JSON model. Understanding the basics of a character's skeleton A character's skeleton is a posable framework of bones. These bones are connected by articulated joints, arranged in a hierarchical data structure. The skeleton is generally rendered and is used as an invisible armature to position and orient a character's skin. The joints are used for relative movement within the skeleton. They are represented by a 4 x 4 linear transformation matrices (combination of rotation, translation, and scale). The character skeleton is set up using only simple rotational joints as they are sufficient to model the joints of real animals. Every joint has limited degrees of freedom (DOFs). DOFs are the possible ranges of motion of an object. For instance, an elbow joint has one rotational DOF and a shoulder joint has three DOFs, as the shoulder can rotate along three perpendicular axes. Individual joints usually have one to six DOFs. Refer to the link http://en.wikipedia.org/wiki/Six_degrees_of_freedom to understand different degrees of freedom. A joint local matrix is constructed for each joint. This matrix defines the position and orientation of each joint and is relative to the joint above it in the hierarchy. The local matrices are used to compute the world space matrices of the joint, using the process of forward kinematics. The world space matrix is used to render the attached geometry and is also used for collision detection. The digital character skeleton is analogous to the real-world skeleton of vertebrates. However, the bones of our digital human character do have to correspond to the actual bones. It will depend on the level of detail of the character you require. For example, you may or may not require cheek bones to animate facial expressions. Skeletons are not just used to animate vertebrates but also mechanical parts such as doors or wheels. Comprehending the joint hierarchy The topology of a skeleton is a tree or an open-directed graph. The joints are connected up in a hierarchical fashion to the selected root joint. The root joint has no parent of itself and is presented in the model JSON file with the parent value of -1. All skeletons are kept as open trees without any closed loops. This restriction though does not prevent kinematic loops. Each node of the tree represents a joint, also called bones. We use both terms interchangeably. For example, the shoulder is a joint, and the upper arm is a bone, but the transformation matrix of both objects is same. So mathematically, we would represent it as a single component with three DOFs. The amount of rotation of the shoulder joint will be reflected by the upper arm's bone. The following figure shows simple robotic bone hierarchy: Understanding forward kinematics Kinematics is a mathematical description of a motion without the underlying physical forces. Kinematics describes the position, velocity, and acceleration of an object. We use kinematics to calculate the position of an individual bone of the skeleton structure (skeleton pose). Hence, we will limit our study to position and orientation. The skeleton is purely a kinematic structure. Forward kinematics is used to compute the world space matrix of each bone from its DOF value. Inverse kinematics is used to calculate the DOF values from the position of the bone in the world. Let's dive a little deeper into forward kinematics and study a simple case of bone hierarchy that starts from the shoulder, moves to the elbow, finally to the wrist. Each bone/joint has a local transformation matrix, this.modelMatrix. This local matrix is calculated from the bone's position and rotation. Let's say the model matrices of the wrist, elbow, and shoulder are this.modelMatrixwrist, this.modelMatrixelbow, and this.modelMatrixshoulder respectively. The world matrix is the transformation matrix that will be used by shaders as the model matrix, as it denotes the position and rotation in world space. The world matrix for a wrist will be: this.worldMatrixwrist = this.worldMatrixelbow * this.modelMatrixwrist The world matrix for an elbow will be: this.worldMatrixelbow = this.worldMatrixshoulder * this.modelMatrixelbow If you look at the preceding equations, you will realize that to calculate the exact location of a wrist in the world space, we need to calculate the position of the elbow in the world space first. To calculate the position of the elbow, we first need to calculate the position of shoulder. We need to calculate the world space coordinate of the parent first in order to calculate that of its children. Hence, we use depth-first tree traversal to traverse the complete skeleton tree starting from its root node. A depth-first traversal begins by calculating modelMatrix of the root node and traverses down through each of its children. A child node is visited and subsequently all of its children are traversed. After all the children are visited, the control is transferred to the parent of modelMatrix. We calculate the world matrix by concatenating the joint parent's world matrix and its local matrix. The computation of calculating a local matrix from DOF and then its world matrix from the parent's world matrix is defined as forward kinematics. Let's now define some important terms that we will often use: Joint DOFs: A movable joint movement can generally be described by six DOFs (three for position and rotation each). DOF is a general term: this.position = vec3.fromValues(x, y, z); this.quaternion = quat.fromValues(x, y, z, w); this.scale = vec3.fromValues(1, 1, 1); We use quaternion rotations to store rotational transformations to avoid issues such as gimbal lock. The quaternion holds the DOF values for rotation around the x, y, and z values. Joint offset: Joints have a fixed offset position in the parent node's space. When we skin a joint, we change the position of each joint to match the mesh. This new fixed position acts as a pivot point for the joint movement. The pivot point of an elbow is at a fixed location relative to the shoulder joint. This position is denoted by a vector position in the joint local matrix and is stored in m31, m32, and m33 indices of the matrix. The offset matrix also holds initial rotational values.
Read more
  • 0
  • 0
  • 11064

article-image-grasping-hammer
Packt
17 Feb 2014
14 min read
Save for later

Grasping Hammer

Packt
17 Feb 2014
14 min read
(For more resources related to this topic, see here.) Terminology In this article, I will be guiding you through many examples using Hammer. There are a handful of terms that will recur many times that you will need to know. It might be a good idea to bookmark this page, so you can flip back and refresh your memory. Brush A brush is a piece of world geometry created with block tool. Brushes make up the basic building blocks of a map and must be convex. A convex object's faces cannot see each other, while a concave object's faces can. Imagine if you're lying on the pitched roof of a generic house. You wouldn't be able to see the other side of the roof because the profile of the house is a convex pentagon. If you moved the point of the roof down inside the house, you would be able to see the other side of the roof because the profile would then be concave. This can be seen in the following screenshot: Got it? Good. Don't think you're limited by this; you can always create convex shapes out of more than one brush. Since we're talking about houses, brushes are used to create the walls, floor, ceiling, and roof. Brushes are usually geometrically very simple; upon creation, common brushes have six faces and eight vertices like a cube. The brushes are outlined in the following screenshot: Not all brushes have six sides; however, the more advanced brushwork techniques can create brushes that have (almost) as many sides as you want. You need to be careful while making complex brushes with the more advanced tools though. This is because it can be quite easy to make an invalid solid if you're not careful. An invalid solid will cause errors during compilation and will make your map unplayable. A concave brush is an example of an invalid solid. World brushes are completely static or unmoving. If you want your brushes to have a special function, they need to be turned into entities. Entity An entity is anything in the game that has a special function. Entities come in two flavors: brush-based and point-based. A brush-based entity, as you can probably guess, is created from a brush. A sliding door or a platform lift are examples of brush-based entities. Point-based entities, on the other hand, are created in a point in space with the entity tool. Lights, models, sounds, and script events are all point-based entities. In the following figure, the models or props are highlighted: World The world is everything inside of a map that you create. Brushes, lights, triggers, sounds, models, and so on are all part of the world. They must all be contained within a sealed map made out of world brushes. Void The void is nothing or everything that isn't the world. The world must be sealed off from the void in order to function correctly when compiled and played in game. Imagine the void as outer space or a vacuum. World brushes seal off the world from the void. If there are any gaps in world brushes (or if there are any entities floating in the void), this will create a leak, and the engine will not be able to discern what the world is and what the void is. If a leak exists in your map, the engine will not know what is supposed to be seen during the compile! The map will compile, but performance-reducing side effects such as bland lighting and excess rendered polygons will plague your map. Settings If at any point in your mapping experience, Hammer doesn't seem to be operating the way you want it to be, go to Tools | Options, and see if there's any preferences you would like to change. You can customize general settings or options related to the 2D and 3D views. If you're coming from another editor, perhaps there's a setting that will make your Hammer experience similar to what you're used to. Loading Hammer for the first time You'll be opening SteamsteamappscommonHalf-Life 2bin often, so you may want to create a desktop shortcut for easier access. Run Hammer.bat from the bin folder to launch Valve Hammer Editor, Valve's map (level) creator, so you can start creating a map. Hammer will prompt you to choose a game configuration, so choose which game you want to map for. I will be using Half-Life 2: Episode Two in the following examples: When you first open up Hammer, you will see a blank gray screen surrounded by a handful of menus, as shown in the following screenshot: Like every other Microsoft Windows application, there is a main menu in the top-left corner of the screen that lets you open, save, or create a document. As far as Hammer is concerned, our documents are maps, and they are saved with the .vmf file extension. So let's open the File menu and load an example map so we can poke around a bit. Load the map titled Chapter2_Example.vmf. The Hammer overview In this section, we will be learning to recognize the different areas of Hammer and what they do. Being familiar with your environment is important! Viewports There are four main windows or viewports in Hammer, as shown in the following screenshot: By default, the top-left window is the 3D view or camera view. The top-right window is the top (x/y) view, the bottom-right window is the side (x/z) view, and the bottom-left window is the front (y/z) view. If you would like to change the layout of the windows, simply click on the top-left corner of any window to change what is displayed. In this article, I will be keeping the default layout but that does not mean you have to! Set up Hammer any way you'd like. For instance, if you would prefer your 3D view to be larger, grab the cross at the middle of the four screens and drag to extend the areas. The 3D window has a few special 3D display types such as Ray-Traced Preview and Lightmap Grid. We will be learning more about these later, but for now, just know that 3D Ray-traced Preview simulates the way light is cast. It does not mimic what you would actually see in-game, but it can be a good first step before compile to see what your lighting may look like. The 3D Lighting Preview will open in a new window and will update every time a camera is moved or a light entity is changed. You cannot navigate directly in the lighting preview window, so you will need to use the 2D cameras to change the viewing perspective. The Map toolbar Located to the left of the screen, the Map toolbar holds all the mapping tools. You will probably use this toolbar the most. The tools will each be covered in depth later on, but here's a basic overview, as shown in the following screenshot, starting from the first tool: The Selection Tool The Selection Tool is pretty self-explanatory; use this tool to select objects. The hot key for this is Shift + S. This is the tool that you will probably use the most. This tool selects objects in the 3D and 2D views and also lets you drag selection boxes in the 2D views. The Magnify Tool The Magnify Tool will zoom in and out in any view. You could also just use the mouse wheel if you have one or the + and – keys on the numerical keypad for the 2D views. The hot key for the magnify tool is Shift + G. The Camera Tool The Camera Tool enables 3D view navigation and lets you place multiple different cameras into the 2D views. Its hot key is Shift + C. The Entity Tool The Entity Tool places entities into the map. If clicked on the 3D view, an entity is placed on the closest brush to the mouse cursor. If used in the 2D view, a crosshair will appear noting the origin of the entity, and the Enter key will add it to the map at the origin. The entity placed in the map is specified by the object bar. The hot key is Shift + E. The Block Tool The Block Tool creates brushes. Drag a box in any 2D view and hit the Enter key to create a brush within the bounds of the box. The object bar specifies which type of brush will be created. The default is box and the hot key for this is Shift + B. The Texture Tool The Texture Tool allows complete control over how you paint your brushes. For now, just know where it is and what it does; the hot key is Shift + A. The Apply Current Texture Tool Clicking on the Apply Current Texture icon will apply the selected texture to the selected brush or brushes. The Decal Tool The Decal Tool applies decals and little detail textures to brushes and the hot key is Shift + D. The Overlay Tool The Overlay Tool is similar to the decal tool. However, overlays are a bit more powerful than decals. Shift + O will be the hot key. The Clipping Tool The Clipping Tool lets you slice brushes into two or more pieces, and the hot key is Shift + X. The Vertex manipulation Tool The Vertex manipulation Tool, or VM tool, allows you to move the individual vertices and edges of brushes any way you like. This is one of the most powerful tools you have in your toolkit! Using this tool improperly, however, is the easiest way to corrupt your map and ruin your day. Not to worry though, we'll learn about this in great detail later on. The hot key is Shift + V. The selection mode bar The selection mode bar (located at the top-right corner by default) lets you choose what you want to select. If Groups is selected, you will select an entire group of objects (if they were previously grouped) when you click on something. The Objects selection mode will only select individual objects within groups. Solids will only select solid objects. The texture bar Located just beneath the selection mode toolbar, the texture bar, as shown, in the following screenshot, shows a thumbnail preview of your currently selected (active) texture and has two buttons that let you select or replace a texture. What a nifty tool, eh? The filter control bar The filter control bar controls your VisGroups (short for visual groups). VisGroups separate your map objects into different categories, similar to layers in the image editing software. To make your mapping life a bit easier, you can toggle visibility of object groups. If, for example, you're trying to sculpt some terrain but keep getting your view blocked by tree models, you can just uncheck the Props box, as shown in the following screenshot, to hide all the trees! There are multiple automatically generated VisGroups such as entities, displacements, and nodraws that you can easily filter through. Don't think you're limited to this though; you can create your own VisGroup with any selection at any time. The object bar The object bar lets you control what type of brush you are creating with the brush tool. This is also where you turn brushes into brush-based entities and create and place prefabs, as shown in the following screenshot: Navigating in 3D You will be spending most of your time in the main four windows, so now let's get comfortable navigating in them, starting with the 3D viewport. Looking around Select the camera tool on the map tools bar. It's the third one down on the list and looks like a red 35 mm camera. Holding the left mouse button while in the 3D view will allow you to look around from a stationary point. Holding the right mouse button will allow you to pan left, right, up, and down in the 3D space. Holding both left and right mouse buttons down together will allow you to move forward and backwards as well as pan left and right. Scrolling the mouse wheel will move the camera forward and backwards. Practice flying down the example map hallway. While looking down the hallway, hold the right mouse button to rise up through the grate and see the top of the map. If you would prefer another method of navigating in 3D, you can use the W, S, A, and D keys to move around while the left mouse button is pressed. Just like your normal FPS game, W moves forward in the direction of the camera, S moves backwards, and A and D move left and right, respectively. You can also move the mouse to look around while moving. Being comfortable with the 3D view is necessary in order to become proficient in creating and scripting 3D environments. As with everything, practice makes perfect, so don't be discouraged if you find yourself hitting the wrong buttons. Having the camera tool selected is not necessary to navigate in 3D. With any tool selected, hold Space bar while the cursor is in the 3D window to activate the 3D navigation mode. While holding Space bar, the navigation functions exactly as it does as if the camera tool was selected. Releasing Space bar will restore normal functionality to the currently selected tool. This can be a huge time saver down the line when you're working on very technical object placements. Multiple cameras If you find yourself bouncing around between different areas of the map, or even changing angles near the same object, you can create multiple cameras and juggle between them. With the camera tool selected, hold Shift and drag a line with the left mouse button in any 2D viewport. The start of the line will be the camera origin, and the end of the line will be the camera's target. Whenever you create a new camera, the newly created camera becomes active and displays its view in the 3D viewport. To cycle between cameras, press the Page Up and Page Down buttons, or click on a camera in any 2D view. Camera locations are stored in the map file when you save, so you don't have to worry about losing them when you exit. Pressing the Delete key with a camera selected will remove the active camera. If you delete the only camera, your view will snap to the origin (0, 0, 0) but you will still be able to look around and create other cameras. In essence, you will always have at least one camera in your map. Selecting objects in the 3D viewport To select an object in the 3D viewport, you must have the selection tool active, as shown in the following screenshot. A quick way to activate the selection tool is to hit the Esc key while any tool is selected, or use the Shift + S hot key. A selected brush or a group of brushes will be highlighted in red with yellow edges as shown in the following screenshot: To deselect anything within the 3D window, click on any other brush, or on the background (void), or simply hit the Esc key. If you want to select an object behind another object, press and hold the left mouse button on the front object. This will cycle through all the objects that are located behind the cursor. You will be able to see the selected objects changing in the 2D and 3D windows about once per second. Simply release the mouse button to complete your selection. To select multiple brushes or objects in the 3D window, hold Ctrl and left-click on multiple brushes. Clicking on a selected object while Ctrl is held will deselect the object. If you've made a mistake choosing objects, you can undo your selections with Ctrl + Z or navigate to Edit | Undo.
Read more
  • 0
  • 0
  • 10765

article-image-prototyping-levels-prototype
Packt
22 Sep 2015
13 min read
Save for later

Prototyping Levels with Prototype

Packt
22 Sep 2015
13 min read
Level design 101 – planning Now, just because we are going to be diving straight into Unity, I feel it's important to talk a little more about how level design is done in the game industry. While you may think a level designer will just jump into the editor and start playing, the truth is you would normally need to do a ton of planning ahead of time before you even open up your tool. Generally, a level begins with an idea. This can come from anything; maybe you saw a really cool building or a photo on the Internet gave you a certain feeling; maybe you want to teach the player a new mechanic. Turning this idea into a level is what a level designer does. Taking all of these ideas, the level designer will create a level design document, which will outline exactly what you're trying to achieve with the entire level from start to end. In this article by John Doran, author of Building FPS Games with Unity, a level design document will describe everything inside the level; listing all of the possible encounters, puzzles, so on and so forth, which the player will need to complete as well as any side quests that the player will be able to achieve. To prepare for this, you should include as many references as you can with maps, images, and movies similar to what you're trying to achieve. If you're working with a team, making this document available on a website or wiki will be a great asset so that you know exactly what is being done in the level, what the team can use in their levels, and how difficult their encounters can be. Generally, you'll also want a top-down layout of your level done either on a computer or with a graph paper, with a line showing a player's general route for the level with the encounters and missions planned out. (For more resources related to this topic, see here.) Of course, you don't want to be too tied down to your design document. It will change as you playtest and work on the level, but the documentation process will help solidify your ideas and give you a firm basis to work from. For those of you interested in seeing some level design documents, feel free to check out Adam Reynolds' Level Designer on Homefront and Call of Duty: World at War at http://wiki.modsrepository.com/index.php?title=Level_Design:_Level_Design_Document_Example. If you want to learn more about level design, I'm a big fan of Beginning Game Level Design by John Feil (previously, my teacher) and Marc Scattergood, Cengage Learning PTR. For more of an introduction to all of game design from scratch, check out Level Up!: The Guide to Great Video Game Design by Scott Rogers and Wiley and The Art of Game Design by Jesse Schel. For some online resources, Scott has a neat GDC talk called Everything I Learned About Level Design I Learned from Disneyland, which can be found at http://mrbossdesign.blogspot.com/2009/03/everything-i-learned-about-game-design.html, and World of Level Design (http://worldofleveldesign.com/) is a good source to learn about level design, though it does not talk about Unity specifically. In addition to a level design document, you can also create a game design document (GDD) that goes beyond the scope of just the level and includes story, characters, objectives, dialogue, concept art, level layouts, and notes about the game's content. However, it is something to do on your own. Creating architecture overview As a level designer, one of the most time-consuming parts of your job will be creating environments. There are many different ways out there to create levels. By default, Unity gives us some default meshes such as a Box, Sphere, and Cylinder. While it's technically possible to build a level in this way, it could get really tedious very quickly. Next, I'm going to quickly go through the most popular options to build levels for the games made in Unity before we jump into building a level of our own. 3D modelling software A lot of times, opening up a 3D modeling software package and building an architecture that way is what professional game studios will often do. This gives you maximum freedom to create your environment and allows you to do exactly what it is you'd like to do; but it requires you to be proficient in that tool, be it Maya, 3ds Max, Blender (which can be downloaded for free at blender.org), or some other tool. Then, you just need to export your models and import them into Unity. Unity supports a lot of different formats for 3D models (most commonly used are .obj and .fbx), but there are a lot of issues to consider. For some best practices when it comes to creating art assets, please visit http://blogs.unity3d.com/2011/09/02/art-assets-best-practice-guide/. Constructing geometry with brushes Constructive Solid Geometry (CSG), commonly referred to as brushes, is a tool artists/designers use to quickly block out pieces of a level from scratch. Using brushes inside the in-game level editor has been a common approach for artists/designers to create levels. Unreal Engine 4, Hammer, Radiant, and other professional game engines make use of this building structure, making it quite easy for people to create and iterate through levels quickly through a process called white-boxing, as it's very easy to make changes to the simple shapes. However; just like learning a modeling software tool, there can be a higher barrier for entry in creating complex geometry using a 3D application, but using CSG brushes will provide a quick solution to create shapes with ease. Unity does not support building things like this by default, but there are several tools in the Unity Asset Store, which allow you to do something like this. For example, sixbyseven studio has an extension called ProBuilder that can add this functionality to Unity, making it very easy to build out levels. The only possible downside is the fact that it does cost money, though it is worth every penny. However, sixbyseven has kindly released a free version of their tools called Prototype, which we installed earlier. It contains everything we will need for this chapter, but it does not allow us to add custom textures and some of the more advanced tools. We will be using ProBuilder later on in the book to polish the entire product. You can find out more information about ProBuilder at http://www.protoolsforunity3d.com/probuilder/. Modular tilesets Another way to generate architecture is through the use of "tiles" that are created by an artist. Similar to using Lego pieces, we can use these tiles to snap together walls and other objects to create a building. With creative uses of the tiles, you can create a large amount of content with just a minimal amount of assets. This is probably the easiest way to create a level at the expense of not being able to create unique looking buildings, since you only have a few pieces to work with. Titles such as Skyrim use this to a great extent to create their large world environments. Mix and match Of course, it's also possible to use a mixture of the preceding tools in order to use the advantages of certain ways of doing things. For example, you could use brushes to block out an area and then use a group of tiles called a tileset to replace the boxes with the highly detailed models, which is what a lot of AAA studios do. In addition, we could initially place brushes to test our gameplay and then add in props to break up the repetitiveness of the levels, which is what we are going to be doing. Creating geometry The first thing we are going to do is to learn how we can create geometry as described in the following steps: From the top menu, go to File | New Scene. This will give us a fresh start to build our project. Next, because we already have Prototype installed, let's create a cube by hitting Ctrl + K. Right now, our Cube (with a name of pb-Cube-1562 or something similar) is placed on a Position of 2, -7, -2. However, for simplicity's sake, I'm going to place it in the middle of the world. We can do this by typing in 0,0,0 by left-clicking in the X position field, typing 0, and then pressing Tab. Notice the cursor is now automatically at the Y part. Type in 0, press Tab again, and then, from the Z slot, press 0 again. Alternatively you can right-click on the Transform component and select Reset Position. Next, we have to center the camera back onto our Cube object. We can do this by going over to the Hierarchy tab and double-clicking on the Cube object (or selecting it and then pressing F). Now, to actually modify this cube, we are going to open up Prototype. We can do this by first selecting our Cube object, going to the Pb_Object component, and then clicking on the green Open Prototype button. Alternatively, you can also go to Tools | Prototype | Prototype Window. This is going to bring up a window much like the one I have displayed here. This new Prototype tab can be detached from the main Unity window or, if you drag from the tab over into Unity, it can be "hooked" into place elsewhere, like the following screenshot shows by my dragging and dropping it to the right of the Hierarchy tab. Next, select the Scene tab in the middle of the screen and press the G key to toggle us into the Object/Geometry mode. Alternatively, you can also click on the Element button in the Scene tab. Unlike the default Object/Top level mode, this will allow us to modify the cube directly to build upon it. For more information on the different modes, check out the Modes & Elements section from http://www.protoolsforunity3d.com/docs/probuilder/#buildingAndEditingGeometry. You'll notice the top of the Prototype tab has three buttons. These stand for what selection type you are currently wanting to use. The default is Vertex or the Point mode, which will allow us to select individual parts to modify. The next is Edge and the last is Face. Face is a good standard to use at this stage, because we only want to extend things out. Select the Face mode by either clicking on the button or pressing the H key twice until it says Editing Faces on the screen. Afterwards, select the box's right side. For a list of keyword shortcuts included with Prototype/ProBuilder, check out http://www.protoolsforunity3d.com/docs/probuilder/#keyboardShortcuts. Now, pull on the red handle to extend our brush outward. Easy enough. Note that, by default, while pulling things out, it is being done in 1 increment. This is nice when we are polishing our levels and trying to make things exactly where we want them, but right now, we are just prototyping. So, getting it out as quickly as possible is paramount to test if it's enjoyable. To help with this, we can use a feature of Unity called Unit Snapping. Undo the previous change we made by pressing Ctrl+Z. Then, move the camera over to the other side and select our longer face. Drag it 9 units out by holding down the Control key (Command on Mac). ProCore3D also has another tool out called ProGrids, which has some advanced unit snapping functionality, but we are not going to be using it. For more information on it, check out http://www.protoolsforunity3d.com/progrids/ If you'd like to change the distance traveled while using unit snapping, set it using the Edit | Snap Settings… menu. Next, drag both the sides out until they are 9 x 9 wide. To make things easier to see, select the Directional Light object in our scene via the Hierarchy tab and reduce the Light component's Intensity to . 5. So, at this point, we have a nice looking floor. However, to create our room, we are first going to need to create our ceiling. Select the floor we have created and press Ctrl + D to duplicate the brush. Once completed, change back into the Object/Top Level editing mode and move the brush so that its Position is at 0, 4, 0. Alternatively, you can click on the duplicated object and, from the Inspector tab, change the Position's Y value to 4. Go back into the sub-selection mode by hitting H to go back to the Faces mode. Then, hold down Ctrl and select all of the edges of our floor. Click on the Extrude button from the Prototype panel. This creates a new part on each of the four edges, which is by default .5 wide (change by clicking on the + button on the edge). This adds additional edges and/or faces to our object. Next, we are going to extrude again; but, rather than doing it from the menu, let's do it manually by selecting the tops of our newly created edges and holding down the Shift button and dragging it up along the Y (green) axis. We then hold down Ctrl after starting the extrusion to have it snap appropriately to fit around our ceiling. Note that the box may not look like this as soon as you let go, as Prototype needs time to compute lighting and materials, which it will mention from the bottom right part of Unity. Next, select Main Camera in the Hierarchy, hit W to switch to the Translate mode, and F to center the selection. Then, move our camera into the room. You'll notice it's completely dark due to the ceiling, but we can add light to the world to fix that! Let's add a point light by going to GameObject | Light | Point Light and position it in the center of the room towards the ceiling (In my case, it was at 4.5, 2.5. 3.5). Then, up the Range to 25 so that it hits the entire room. Finally, add a player to see how he interacts. First, delete the Main Camera object from Hierarchy, as we won't need it. Then, go into the Project tab and open up the AssetsUFPSBaseContentPrefabsPlayers folder. Drag and drop the AdvancedPlayer prefab, moving it so that it doesn't collide with the walls, floors, or ceiling, a little higher than the ground as shown in the following screenshot: Next, save our level (Chapter 3_1_CreatingGeometry) and hit the Play button. It may be a good idea for you to save your levels in such a way that you are able to go back and see what was covered in each section for each chapter, thus making things easier to find in the future. Again, remember that we can pull a weapon out by pressing the 1-5 keys. With this, we now have a simple room that we can interact with! Summary In this article, we take on the role of a level designer, who has been asked to create a level prototype to prove that our gameplay is solid. We will use the free Prototype tool to help in this endeavor. In addition, we will also learn some beginning level designs. Resources for Article: Further resources on this subject: Unity Networking – The Pong Game [article] Unity 3.x Scripting-Character Controller versus Rigidbody [article] Animations in Cocos2d-x [article]
Read more
  • 0
  • 0
  • 10546

article-image-xna-4-3dgetting-battle-tanks-game-world
Packt
20 Sep 2012
16 min read
Save for later

XNA 4-3D:Getting the battle-tanks into game world

Packt
20 Sep 2012
16 min read
Adding the tank model For tank battles, we will be using a 3D model available for download from the App Hub website (http://create.msdn.com) in the Simple Animation CODE SAMPLE available at http://xbox.create.msdn.com/en-US/education/catalog/sample/simple_animation. Our first step will be to add the model to our content project in order to bring it into the game. Time for action – adding the tank model We can add the tank model to our project by following these steps: Download the 7089_06_GRAPHICSPACK.ZIP file from the book's website and extract the contents to a temporary folder. Select the .fbx file and the two .tga files from the archive and copy them to the Windows clipboard. Switch to Visual Studio and expand the Tank BattlesContent (Content) project. Right-click on the Models folder and select Paste to copy the files on the clipboard into the folder. Right-click on engine_diff_tex.tga inside the Models folder and select Exclude From Project. Right click on turret_alt_diff_tex.tga inside the Models folder and select Exclude From Project. What just happened? Adding a model to our game is like adding any other type of content, though there are a couple of pitfalls to watch out for. Our model includes two image files (the .tga files&emdash;an image format commonly associated with 3D graphics files because the format is not encumbered by patents) that will provide texture maps for the tank's surfaces. Unlike the other textures we have used, we do not want to include them as part of our content project. Why not? The content processor for models will parse the .fbx file (an Autodesk file format used by several 3D modeling packages) at compile time and look for the textures it references in the directory the model is in. It will automatically process these into .xnb files that are placed in the output folder &endash; Models, for our game. If we were to also include these textures in our content project, the standard texture processor would convert the image just like it does with the textures we normally use. When the model processor comes along and tries to convert the texture, an .xnb file with the same name will already exist in the Models folder, causing compile time errors. Incidentally, even though the images associated with our model are not included in our content project directly, they still get built by the content pipeline and stored in the output directory as .xnb files. They can be loaded just like any other Texture2D object with the Content.Load() method. Free 3D modeling software There are a number of freely available 3D modeling packages downloadable on the Web that you can use to create your own 3D content. Some of these include:   Blender: A free, open source 3D modeling and animation package. Feature rich, and very powerful. Blender can be found at http://www.blender.org. Wings 3D: Free, open source 3D modeling package. Does not support animation, but includes many useful modeling features. Wings 3D can be found at http://wings3d.com. Softimage Mod Tool: A modeling and animation package from Autodesk. The Softimage Mod Tool is available freely for non-commercial use. A version with a commercial-friendly license is also available to XNA Creator's Club members at http://usa.autodesk.com/adsk/servlet/pc/item?id=13571257&siteID=123112.         Building tanks Now that the model is part of our project, we need to create a class that will manage everything about a tank. While we could simply load the model in our TankBattlesGame class, we need more than one tank, and duplicating all of the items necessary to handle both tanks does not make sense. Time for action – building the Tank class We can build the Tank class using the following steps: Add a new class file called Tank.cs to the Tank Battles project. Add the following using directives to the top of the Tank.cs class file: using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; Add the following fields to the Tank class: #region Fields private Model model; private GraphicsDevice device; private Vector3 position; private float tankRotation; private float turretRotation; private float gunElevation; private Matrix baseTurretTransform; private Matrix baseGunTransform; private Matrix[] boneTransforms; #endregion Add the following properties to the Tank class: #region Properties public Vector3 Position { get { return position; } set { position = value; } } public float TankRotation { get { return tankRotation; } set { tankRotation = MathHelper.WrapAngle(value); } } public float TurretRotation { get { return turretRotation; } set { turretRotation = MathHelper.WrapAngle(value); } } public float GunElevation { get { return gunElevation; } set { gunElevation = MathHelper.Clamp( value, MathHelper.ToRadians(-90), MathHelper.ToRadians(0)); } } #endregion Add the Draw() method to the Tank class, as follows: #region Draw public void Draw(ArcBallCamera camera) { model.Root.Transform = Matrix.Identity * Matrix.CreateScale(0.005f) * Matrix.CreateRotationY(TankRotation) * Matrix.CreateTranslation(Position); model.CopyAbsoluteBoneTransformsTo(boneTransforms); foreach (ModelMesh mesh in model.Meshes) { foreach (BasicEffect basicEffect in mesh.Effects) { basicEffect.World = boneTransforms[mesh.ParentBone. Index]; basicEffect.View = camera.View; basicEffect.Projection = camera.Projection; basicEffect.EnableDefaultLighting(); } mesh.Draw(); } } #endregion In the declarations area of the TankBattlesGame class, add a new List object to hold a list of Tank objects, as follows: List tanks = new List(); Create a temporary tank so we can see it in action by adding the following to the end of the LoadContent() method of the TankBattlesGame class: tanks.Add( new Tank( GraphicsDevice, Content.Load(@"Modelstank"), new Vector3(61, 40, 61))); In the Draw() method of the TankBattlesGame class, add a loop to draw all of the Tank objects in the tank's list after the terrain has been drawn, as follows: foreach (Tank tank in tanks) { tank.Draw(camera); } Execute the game. Use your mouse to rotate and zoom in on the tank floating above the top of the central mountain in the scene, as shown in the following screenshot: What just happened? The Tank class stores the model that will be used to draw the tank in the model field. Just as with our terrain, we need a reference to the game's GraphicsDevice in order to draw our model when necessary. In addition to this information, we have fields (and corresponding properties) to represent the position of the tank, and the rotation angle of three components of the model. The first, TankRotation, determines the angle at which the entire tank is rotated. As the turret of the tank can rotate independently of the direction in which the tank itself is facing, we store the rotation angle of the turret in TurretRotation. Both TankRotation and TurretRotation contain code in their property setters to wrap their angles around if we go past a full circle in either direction. The last angle we want to track is the elevation angle of the gun attached to the turret. This angle can range from 0 degrees (pointing straight out from the side of the turret) to -90 degrees (pointing straight up). This angle is stored in the GunElevation property. The last field added in step 3 is called boneTransforms, and is an array of matrices. We further define this array while defining the Tank class' constructor by creating an empty array with a number of elements equal to the number of bones in the model. But what exactly are bones? When a 3D artist creates a model, they can define joints that determine how the various pieces of the model are connected. This process is referred to as "rigging" the model, and a model that has been set up this way is sometimes referred to as "rigged for animation". The bones in the model are defined with relationships to each other, so that when a bone higher up in the hierarchy moves, all of the lower bones are moved in relation to it. Think for a moment of one of your fingers. It is composed of three distinct bones separated by joints. If you move the bone nearest to your palm, the other two bones move as well – they have to if your finger bones are going to stay connected! The same is true of the components in our tank. When the tank rotates, all of its pieces rotate as well. Rotating the turret moves the cannon, but has no effect on the body or the wheels. Moving the cannon has no effect on any other parts of the model, but it is hinged at its base, so that rotating the cannon joint makes the cannon appear to elevate up and down around one end instead of spinning around its center. We will come back to these bones in just a moment, but let's first look at the current Draw() method before we expand it to account for bone-based animation. Model.Root refers to the highest level bone in the model's hierarchy. Transforming this bone will transform the entire model, so our basic scaling, rotation, and positioning happen here. Notice that we are drastically scaling down the model of the tank, to a scale of 0.005f. The tank model is quite large in raw units, so we need to scale it to a size that is in line with the scale we used for our terrain. Next, we use the boneTransforms array we created earlier by calling the model's CopyAbsoluteBoneTransformsTo() method. This method calculates the resultant transforms for each of the bones in the model, taking into account all of the parent bones above it, and copies these values into the specified array. We then loop through each mesh in the model. A mesh is an independent piece of the model, representing a movable part. Each of these meshes can have multiple effects tied to it, so we loop through those as well, using an instance of BasicEffect created on the spot to render the meshes. In order to render each mesh, we establish the mesh's world location by looking up the mesh's parent bone transformation and storing it in the World matrix. We apply our View and Projection matrices just like before, and enable default lighting on the effect. Finally, we draw the mesh, which sends the triangles making up this portion of the model out to the graphics card. The tank model The tank model we are using is from the Simple Animation sample for XNA 4.0, available on Microsoft's MSDN website at http://xbox.create.msdn.com/en-US/education/catalog/sample/simple_animation. Bringing things down to earth You might have noticed that our tank is not actually sitting on the ground. In fact, we have set our terrain scaling so that the highest point in the terrain is at 30 units, while the tank is positioned at 40 units above the X-Z plane. Given a (X,Z) coordinate pair, we need to come up with a way to determine what height we should place our tank at, based on the terrain. Time for action – terrain heights To place our tank appropriately on the terrain, we first need to calculate, then place our tank there. This is done in the following steps: Add a helper method to the Terrain class to calculate the height based on a given coordinate as follows: #region Helper Methods public float GetHeight(float x, float z) { int xmin = (int)Math.Floor(x); int xmax = xmin + 1; int zmin = (int)Math.Floor(z); int zmax = zmin + 1; if ( (xmin < 0) || (zmin < 0) || (xmax > heights.GetUpperBound(0)) || (zmax > heights.GetUpperBound(1))) { return 0; } Vector3 p1 = new Vector3(xmin, heights[xmin, zmax], zmax); Vector3 p2 = new Vector3(xmax, heights[xmax, zmin], zmin); Vector3 p3; if ((x - xmin) + (z - zmin) <= 1) { p3 = new Vector3(xmin, heights[xmin, zmin], zmin); } else { p3 = new Vector3(xmax, heights[xmax, zmax], zmax); } Plane plane = new Plane(p1, p2, p3); Ray ray = new Ray(new Vector3(x, 0, z), Vector3.Up); float? height = ray.Intersects(plane); return height.HasValue ? height.Value : 0f; } #endregion In the LoadContent() method of the TankBattlesGame class, modify the statement that adds a tank to the battlefield to utilize the GetHeight() method as follows: tanks.Add( new Tank( GraphicsDevice, Content.Load(@"Modelstank"), new Vector3(61, terrain.GetHeight(61,61), 61))); Execute the game and view the tank, now placed on the terrain as shown in the following screenshot: What just happened? You might be tempted to simply grab the nearest (X, Z) coordinate from the heights[] array in the Terrain class and use that as the height for the tank. In fact, in many cases that might work. You could also average the four surrounding points and use that height, which would account for very steep slopes. The drawbacks with those approaches will not be entirely evident in Tank Battles, as our tanks are stationary. If the tanks were mobile, you would see the elevation of the tank jump between heights jarringly as the tank moved across the terrain because each virtual square of terrain that the tank entered would have only one height. In the GetHeight() method that we just saw, we take a different approach. Recall that the way our terrain is laid out, it grows along the positive X and Z axes. If we imagine looking down from a positive Y height onto our terrain with an orientation where the X axis grows to the right and the Z axis grows downward, we would have something like the following: As we discussed when we created our index buffer, our terrain is divided up into squares whose corners are exactly 1 unit apart. Unfortunately, these squares do not help us in determining the exact height of any given point, because each of the four points of the square can theoretically have any height from 0 to 30 in the case of our terrain scale. Remember though, that each square is divided into two triangles. The triangle is the basic unit of drawing for our 3D graphics. Each triangle is composed of three points, and we know that three points can be used to define a plane. We can use XNA's Plane class to represent the plane defined by an individual triangle on our terrain mesh. To do so, we just need to know which triangle we want to use to create the plane. In order to determine this, we first get the (X, Z) coordinates (relative to the view in the preceding figure) of the upper-left corner of the square our point is located in. We determine this point by dropping any fractional part of the x and z coordinates and storing the values in xmin and zmin for later use. We check to make sure that the values we will be looking up in the heights[] array are valid (greater than zero and less than or equal to the highest element in each direction in the array). This could happen if we ask for the height of a position that is outside the bounds of our map's height. Instead of crashing the game, we will simply return a zero. It should not happen in our code, but it is better to account for the possibility than be surprised later. We define three points, represented as Vector3 values p1, p2, and p3. We can see right away that no matter which of the two triangles we pick, the (xmax, zmin) and (xmin, zmax) points will be included in our plane, so their values are set right away. To decide which of the final two points to use, we need to determine which side of the central dividing line the point we are looking for lies in. This actually turns out to be fairly simple to do for the squares we are using. In the case of our triangle, if we eliminate the integer portion of our X and Z coordinates (leaving only the fractional part that tells us how far into the square we are), the sum of both of these values will be less than or equal to the size of one grid square (1 in our case) if we are in the upper left triangle. Otherwise our point is in the right triangle. The code if ((x - xmin) + (z - zmin) <= 1) performs this check, and sets the value of p3 to either (xmin, zmin) or (xmax, zmax) depending on the result. Once we have our three points, we ask XNA to construct a Plane using them, and then we construct another new type of object we have not yet used – an object of the Ray class. A Ray has a base point, represented by a Vector3, and a direction – also represented by a Vector3. Think of a Ray as an infinitely long arrow that starts somewhere in our world and heads off in a given direction forever. In the case of the Ray we are using, the starting point is at the zero point on the Y axis, and the coordinates we passed into the method for X and Z. We specify Vector3.Up as the direction the Ray is pointing in. Remember from the FPS camera that Vector3.Up has an actual value of (0, 1, 0), or pointing up along the positive Y axis. The Ray class has an Intersects() method that returns the distance from the origin point along the Ray where the Ray intersects a given Plane. We must assign the return value of this method to a float? instead of a normal float. You may not be familiar with this notation, but the question mark at the end of the type specifies that the value is nullable—that is, it might contain a value, but it could also just contain a null value. In the case of the Ray.Intersects() method, the method will return null if the object of Ray class does not intersect the object of the Plane class at any point. This should never happen with our terrain height code, but we need to account for the possibility. When using a nullable float, we need to check to make sure that the variable actually has a value before trying to use it. In this case, we use the HasValue property of the variable. If it does have one, we return it. Otherwise we return a default value of zero.
Read more
  • 0
  • 0
  • 10496
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
article-image-importing-3d-formats-away3d
Packt
31 May 2011
5 min read
Save for later

Importing 3D Formats into Away3D

Packt
31 May 2011
5 min read
Away3D 3.6 Cookbook Over 80 practical recipes for creating stunning graphics and effects with the fascinating Away3D engine Introduction The Away3D library contains a large set of 3D geometric primitives such as Cube, Sphere, Plane, and many more. Nevertheless, when we think of developing breathtaking and cutting edge 3D applications, there is really no way to get it done without using more sophisticated models than just basic primitives. Therefore, we need to use external 3D modeling programs such as Autodesk 3DsMax and Maya, or Blender to create complex models. The Power of Away3D is that it allows us to import a wide range of 3D formats for static meshes as well as for animations. Besides the models, the not less important part of the 3D world is textures. They are critical in making the model look cool and influencing the ultimate user experience. In this article, you will learn essential techniques to import different 3D formats into Away3D. Exporting models from 3DsMax/Maya/Blender You can export the following modeling formats from 3D programs: (Wavefront), Obj, DAE (Collada), 3ds, Ase (ASCII), MD2, Kmz, 3DsMax, and Maya can export natively Obj, DAE, 3ds, and ASCII. One of the favorite 3D formats of Away3D developers is DAE (Collada), although it is not the best in terms of performance because the file is basically an XML which becomes slow to parse when containing a lot of data. The problem is that although 3DsMax and Maya have got a built-in Collada exporter, the models from the output do not work in Away3D. The work around is to use open source Collada exporters such as ColladaMax/ColladaMaya, OpenCollada. The only difference between these two is the software versions support. Getting ready Go to http://opencollada.org/download.html and download the OpenCollada plugin for the appropriate software (3DsMax or Maya). Go to http://sourceforge.net/projects/colladamaya/files/ and download the ColladaMax or colladamaya plugin. Follow the instructions of the installation dialog of the plugin. The plugin will get installed automatically in the 3dsMax/Maya plugins directory (taking into account that the software was installed into the default path). How to do it... 3DsMax: Here is how to export Collada using OpenCollada plugin in 3DsMax2011. In order to export Collada (DAE) from 3DsMax, you should do the following: In 3DsMax, go to File and click on Export or Export Selected (target model selected). Select the OpenCOLLADA(*.DAE) format from the formats drop-down list. ColladaMax export settings: (Currently 3DsMax 2009 and lower) ColladaMax export settings are almost the same as those of OpenCollada. The only difference you can see in the exporting interface is the lack of Copy Images and Export user defined properties checkboxes. Select the checkboxes as is shown in the previous screenshot. Relative paths: Makes sure the texture paths are relative. Normals: Exporting object's normals. Copy Images: Is optional. If we select this option, the exporter outputs a folder with related textures into the same directory as the exported object. Triangulate: In case some parts of the mesh consist of more than three angled polygons, they get triangulated. Animation settings: Away3D supports bones animations from external assets. If you set bones animation and wish to export it, then check the Sample animation and set the begin and end frame for animation span that you want to export from the 3DsMax animation timeline. Maya: For showcase purposes, you can download a 30-day trial version of Autodesk Maya 2011. The installation process in Maya is slightly different: Open Maya. Go to top menu bar and select Window. In the drop-down list, select Settings/Preferences, in the new drop-down list, select Plug-in manager. Now you should see the Plug-in Manager interface: Now click on the Browse button and navigate to the directory where you extracted the OpenCollada ZIP archive. Select the COLLADAMaya.mll file and open it. Now you should see the OpenCollada plugin under the Other Registered Plugins category. Check the AutoLoad checkbox if you wish for the plugin to be loaded automatically the next time you start the program. After your model is ready for export, click on File | Export All or Export selected. The export settings for ColladaMaya are the same as for 3DsMax. How it works... The Collada file is just another XML but with a different format name (.dae). When exporting a model in a Collada format, the exporter writes into the XML nodes tree all essential data describing the model structure as well as animation data when one exports bone-based animated models. When deploying your DAE models to the web hosting directory, don't forget to change the .DAE extension to .XML. Forgetting will result in the file not being able to load because .DAE extension is ignored by most servers by default. There's more... Besides the Collada, you can also export OBJ, 3Ds, and ASE. Fortunately, for exporting these formats, you don't need any third party plugins but only those already located in the software. Free programs such as Blender also serve as an alternative to expansive commercial software such as Maya, or 3DsMax Blender comes with already built-in Collada exporter. Actually, it has two such exporters. At the time of this writing, these are 1.3 and 1.4. You should use 1.4 as 1.3 seems to output corrupted files that are not parsed in Away3D. The export process looks exactly like the one for 3dsMax. Select your model. Go to File, then Export. In the drop-down list of different formats, select Collada 1.4. The following interface opens: Select Triangles, Only Export Selection (if you wish to export only selected object), and Sample Animation. Set exporting destination path and click on Export and close. You are done.
Read more
  • 0
  • 0
  • 10361

article-image-enemy-and-friendly-ais
Packt
17 Dec 2014
29 min read
Save for later

Enemy and Friendly AIs

Packt
17 Dec 2014
29 min read
In this article by Kyle D'Aoust, author of the book Unity Game Development Scripting, we will see how to create enemy and friendly AIs. (For more resources related to this topic, see here.) Artificial Intelligence, also known as AI, is something that you'll see in every video game that you play. First-person shooter, real-time strategy, simulation, role playing games, sports, puzzles, and so on, all have various forms of AI in both large and small systems. In this article, we'll be going over several topics that involve creating AI, including techniques, actions, pathfinding, animations, and the AI manager. Then, finally, we'll put it all together to create an AI package of our own. In this article, you will learn: What a finite state machine is What a behavior tree is How to combine two AI techniques for complex AI How to deal with internal and external actions How to handle outside actions that affect the AI How to play character animations What is pathfinding? How to use a waypoint system How to use Unity's NavMesh pathfinding system How to combine waypoints and NavMesh for complete pathfinding AI techniques There are two very common techniques used to create AI: the finite state machine and the behavior tree. Depending on the game that you are making and the complexity of the AI that you want, the technique you use will vary. In this article, we'll utilize both the techniques in our AI script to maximize the potential of our AI. Finite state machines Finite state machines are one of the most common AI systems used throughout computer programming. To define the term itself, a finite state machine breaks down to a system, which controls an object that has a limited number of states to exist in. Some real-world examples of a finite state machine are traffic lights, television, and a computer. Let's look at an example of a computer finite state machine to get a better understanding. A computer can be in various states. To keep it simple, we will list three main states. These states are On, Off, and Active. The Off state is when the computer does not have power running it, the On state is when the computer does have power running it, and the Active state is when someone is using the computer. Let's take a further look into our computer finite state machine and explore the functions of each of its states: State Functions On Can be used by anyone Can turn off the computer Off Can turn on the computer Computer parts can be operated on Active Can access the Internet and various programs Can communicate with other devices Can turn off the computer Each state has its own functions. Some of the functions of each state affect each other, while some do not. The functions that do affect each other are the functions that control what state the finite state machine is in. If you press the power button on your computer, it will turn on and change the state of your computer to On. While the state of your computer is On, you can use the Internet and possibly some other programs, or communicate to other devices such as a router or printer. Doing so will change the state of your computer to Active. When you are using the computer, you can also turn off the computer by its software or by pressing the power button, therefore changing the state to Off. In video games, you can use a finite state machine to create AI with a simple logic. You can also combine finite state machines with other types of AI systems to create a unique and perhaps more complex AI system. In this article, we will be using finite state machines as well as what is known as a behavior tree. The behavior tree form of the AI system A behavior tree is another kind of AI system that works in a very similar way to finite state machines. Actually, behavior trees are made up of finite state machines that work in a hierarchical system. This system of hierarchy gives us great control over an individual, and perhaps many finite state systems within the behavior tree, allowing us to have a complex AI system. Taking a look back at the table explaining a finite state machine, a behavior tree works the same way. Instead of states, you have behaviors, and in place of the state functions, you have various finite state machines that determine what is done while the AI is in a specific behavior. Let's take a look at the behavior tree that we will be using in this article to create our AI: On the left-hand side, we have four behaviors: Idle, Guard, Combat, and Flee. To the right are the finite state machines that make up each of the behaviors. Idle and Flee only have one finite state machine, while Guard and Combat have multiple. Within the Combat behavior, two of its finite state machines even have a couple of their own finite state machines. As you can see, this hierarchy-based system of finite state machines allows us to use a basic form of logic to create an even more complex AI system. At the same time, we are also getting a lot of control by separating our AI into various behaviors. Each behavior will run its own silo of code, oblivious to the other behaviors. The only time we want a behavior to notice another behavior is either when an internal or external action occurs that forces the behavior of our AI to change. Combining the techniques In this article, we will take both of the AI techniques and combine them to create a great AI package. Our behavior tree will utilize finite state machines to run the individual behaviors, creating a unique and complex AI system. This AI package can be used for an enemy AI as well as a friendly AI. Let's start scripting! Now, let's begin scripting our AI! To start off, create a new C# file and name it AI_Agent. Upon opening it, delete any functions within the main class, leaving it empty. Just after the using statements, add this enum to the script: public enum Behaviors {Idle, Guard, Combat, Flee}; This enum will be used throughout our script to determine what behavior our AI is in. Now let's add it to our class. It is time to declare our first variable: public Behaviors aiBehaviors = Behaviors.Idle; This variable, aiBehaviors, will be the deciding factor of what our AI does. Its main purpose is to have its value checked and changed when needed. Let's create our first function, which will utilize one of this variable's purposes: void RunBehaviors(){switch(aiBehaviors){case Behaviors.Idle:   RunIdleNode();   break;case Behaviors.Guard:   RunGuardNode();   break;case Behaviors.Combat:   RunCombatNode();   break;case Behaviors.Flee:   RunFleeNode();   break;}} What this function will do is check the value of our aiBehaviors variable in a switch statement. Depending on what the value is, it will then call a function to be used within that behavior. This function is actually going to be a finite state machine, which will decide what that behavior does at that point. Now, let's add another function to our script, which will allow us to change the behavior of our AI: void ChangeBehavior(Behaviors newBehavior){aiBehaviors = newBehavior; RunBehaviors();} As you can see, this function works very similarly to the RunBehaviors function. When this function is called, it will take a new behaviors variable and assign its value to aiBehaviors. By doing this, we changed the behavior of our AI. Now let's add the final step to running our behaviors; for now, they will be empty functions that act as placeholders for our internal and external actions. Add these functions to the script: void RunIdleNode(){ } void RunGuardNode(){ }void RunCombatNode(){ }void RunFleeNode(){ } Each of these functions will run the finite state machines that make up the behaviors. These functions are essentially a middleman between the behavior and the behavior's action. Using these functions is the beginning of having more control over our behaviors, something that can't be done with a simple finite state machine. Internal and external actions The actions of a finite state machine can be broken up into internal and external actions. Separating the actions into the two categories makes it easier to define what our AI does in any given situation. The separation is helpful in the planning phase of creating AI, but it can also help in the scripting part as well, since you will know what will and will not be called by other classes and GameObjects. Another way this separation is beneficial is that it eases the work of multiple programmers working on the same AI; each programmer could work on separate parts of the AI without as many conflicts. External actions External actions are functions and activities that are activated when objects outside of the AI object act upon the AI object. Some examples of external actions include being hit by a player, having a spell being cast upon the player, falling from heights, losing the game by an external condition, communicating with external objects, and so on. The external actions that we will be using for our AI are: Changing its health Raising a stat Lowering a stat Killing the AI Internal actions Internal actions are the functions and activities that the AI runs within itself. Examples of these are patrolling a set path, attacking a player, running away from the player, using items, and so on. These are all actions that the AI will choose to do depending on a number of conditions. The internal actions that we will be using for our AI are: Patrolling a path Attacking a player Fleeing from a player Searching for a player Scripting the actions It's time to add some internal and external actions to the script. First, be sure to add the using statement to the top of your script with the other using statements: using System.Collections.Generic; Now, let's add some variables that will allow us to use the actions: public bool isSuspicious = false;public bool isInRange = false;public bool FightsRanged = false;public List<KeyValuePair<string, int>> Stats = new List<KeyValuePair<string, int>>();public GameObject Projectile; The first three of our new variables are conditions to be used in finite state machines to determine what function should be called. Next, we have a list of the KeyValuePair variables, which will hold the stats of our AI GameObject. The last variable is a GameObject, which is what we will use as a projectile for ranged attacks. Remember the empty middleman functions that we previously created? Now with these new variables, we will be adding some code to each of them. Add this code so that the empty functions are now filled: void RunIdleNode(){Idle();} void RunGuardNode(){Guard();}void RunCombatNode(){if(FightsRanged)   RangedAttack();else   MeleeAttack();}void RunFleeNode(){Flee();} Two of the three boolean variables we just created are being used as conditionals to call different functions, effectively creating finite state machines. Next, we will be adding the rest of our actions; these are what is being called by the middleman functions. Some of these functions will be empty placeholders, but will be filled later on in the article: void Idle(){} void Guard(){if(isSuspicious){   SearchForTarget();}else{   Patrol();}}void Combat(){if(isInRange){   if(FightsRanged)   {     RangedAttack();   }   else   {     MeleeAttack();   }}else{   SearchForTarget();}}void Flee(){} void SearchForTarget(){} void Patrol(){} void RangedAttack(){GameObject newProjectile;newProjectile = Instantiate(Projectile, transform.position, Quaternion.identity) as GameObject;} void MeleeAttack(){} In the Guard function, we check to see whether the AI notices the player or not. If it does, then it will proceed to search for the player; if not, then it will continue to patrol along its path. In the Combat function, we first check to see whether the player is within the attacking range; if not, then the AI searches again. If the player is within the attacking range, we check to see whether the AI prefers attacking up close or far away. For ranged attacks, we first create a new, temporary GameObject variable. Then, we set it to an instantiated clone of our Projectile GameObject. From here, the projectile will run its own scripts to determine what it does. This is how we allow our AI to attack the player from a distance. To finish off our actions, we have two more functions to add. The first one will be to change the health of the AI, which is as follows: void ChangeHealth(int Amount){if(Amount < 0){   if(!isSuspicious)   {     isSuspicious = true;     ChangeBehavior(Behaviors.Guard);   }}for(int i = 0; i < Stats.Capacity; i++){   if(Stats[i].Key == "Health")   {     int tempValue = Stats[i].Value;     Stats[i] = new KeyValuePair<string, int>(Stats[i].Key, tempValue += Amount);     if(Stats[i].Value <= 0)     {       Destroy(gameObject);     }     else if(Stats[i].Value < 25)     {       isSuspicious = false;       ChangeBehavior(Behaviors.Flee);     }     break;   }}} This function takes an int variable, which is the amount by which we want to change the health of the player. The first thing we do is check to see if the amount is negative; if it is, then we make our AI suspicious and change the behavior accordingly. Next, we search for the health stat in our list and set its value to a new value that is affected by the Amount variable. We then check if the AI's health is below zero to kill it; if not, then we also check if its health is below 25. If the health is that low, we make our AI flee from the player. To finish off our actions, we have one last function to add. It will allow us to affect a specific stat of the AI. These modifications will either add to or subtract from a stat. The modifications can be permanent or restored anytime. For the following instance, the modifications will be permanent: void ModifyStat(string Stat, int Amount){for(int i = 0; i < Stats.Capacity; i++){   if(Stats[i].Key == Stat)   {     int tempValue = Stats[i].Value;     Stats[i] = new KeyValuePair<string, int>(Stats[i].Key, tempValue += Amount);     break;   }}if(Amount < 0){   if(!isSuspicious)   {     isSuspicious = true;     ChangeBehavior(Behaviors.Guard);   }}} This function takes a string and an integer. The string is used to search for the specific stat that we want to affect and the integer is how much we want to affect that stat by. It works in a very similar way to how the ChangeHealth function works, except that we first search for a specific stat. We also check to see if the amount is negative. This time, if it is negative, we change our AI behavior to Guard. This seems to be an appropriate response for the AI after being hit by something that negated one of its stats! Pathfinding Pathfinding is how the AI will maneuver around the level. For our AI package, we will be using two different kinds of pathfinding, NavMesh and waypoints. The waypoint system is a common approach to create paths for AI to move around the game level. To allow our AI to move through our level in an intelligent manner, we will use Unity's NavMesh component. Creating paths using the waypoint system Using waypoints to create paths is a common practice in game design, and it's simple too. To sum it up, you place objects or set locations around the game world; these are your waypoints. In the code, you will place all of your waypoints that you created in a container of some kind, such as a list or an array. Then, starting at the first waypoint, you tell the AI to move to the next waypoint. Once that waypoint has been reached, you send the AI off to another one, ultimately creating a system that iterates through all of the waypoints, allowing the AI to move around the game world through the set paths. Although using the waypoint system will grant our AI movement in the world, at this point, it doesn't know how to avoid obstacles that it may come across. That is when you need to implement some sort of mesh navigation system so that the AI won't get stuck anywhere. Unity's NavMesh system The next step in creating AI pathfinding is to create a way for our AI to navigate through the game world intelligently, meaning that it does not get stuck anywhere. In just about every game out there that has a 3D-based AI, the world it inhabits has all sorts of obstacles. These obstacles could be plants, stairs, ramps, boxes, holes, and so on. To get our AI to avoid these obstacles, we will use Unity's NavMesh system, which is built into Unity itself. Setting up the environment Before we can start creating our pathfinding system, we need to create a level for our AI to move around in. To do this, I am just using Unity primitive models such as cubes and capsules. For the floor, create a cube, stretch it out, and squish it to make a rectangle. From there, clone it several times so that you have a large floor made up of cubes. Next, delete a bunch of the cubes and move some others around. This will create holes in our floor, which will be used and tested when we implement the NavMesh system. To make the floor easy to see, I've created a material in green and assigned it to the floor cubes. After this, create a few more cubes, make one really long and one shorter than the previous one but thicker, and the last one will be used as a ramp. I've created an intersection of the really long cube and the thick cube. Then, place the ramp towards the end of the thick cube, giving access to the top of the cubes. Our final step in creating our test environment is to add a few waypoints for our AI. For testing purposes, create five waypoints in this manner. Place one in each corner of the level and one in the middle. For the actual waypoints, use the capsule primitive. For each waypoint, add a rigid body component. Name the waypoints as Waypoint1, Waypoint2, Waypoint3, and so on. The name is not all that important for our code; it just makes it easier to distinguish between waypoints in the inspector. Here's what I made for my level:  Creating the NavMesh Now, we will create the navigation mesh for our scene. The first thing we will do is select all of the floor cubes. In the menu tab in Unity, click on the Window option, and then click on the Navigation option at the bottom of the dropdown; this will open up the Navigation window. This is what you should be seeing right now:  By default, the OffMeshLink Generation option is not checked; be sure to check it. What this does is create links at the edges of the mesh allowing it to communicate with any other OffMeshLink nearby, creating a singular mesh. This is a handy tool since game levels typically use more than one mesh as a floor. The Scene filter will just show specific objects within the hierarchy view list. Selecting all the objects will show all of your GameObjects. Selecting mesh renderers will only show GameObjects that have the mesh renderer component. Then, finally, if you select terrains, only terrains will be shown in the Hierarchy view list. The Navigation Layer dropdown will allow you to set the area as either walkable, not walkable, or jump accessible. Walkable areas typically refer to floors, ramps, and so on. Non-walkable areas refer to walls, rocks, and other various obstacles. Next, click on the Bake tab next to the Object tab. You should see information that looks like this: For this article, I am leaving all the values at their defaults. The Radius property is used to determine how close to the walls the navigation mesh will exist. Height determines how much vertical space is needed for the AI agent to be able to walk on the navigation mesh. Max Slope is the maximum angle that the AI is allowed to travel on for ramps, hills, and so on. The Step Height property is used to determine how high the AI can step up onto surfaces higher than the ground level. For Generated Off Mesh Links, the properties are very similar to each other. The Drop Height value is the maximum amount of space the AI can intelligently drop down to another part of the navigation mesh. Jump Distance is the opposite of Height; it determines how high the AI can jump up to another part of the navigation mesh. The Advanced options are to be used when you have a better understanding of the NavMesh component and want a little more out of it. Here, you can further tweak the accuracy of the NavMesh as well as create Height Mesh to coincide with the navigation mesh. Now that you know all the basics of the Unity NavMesh, let's go ahead and create our navigation mesh. At the bottom-right corner of the Navigation tab in the Inspector window, you should see two buttons: one that says Clear and the other that says Bake. Click on the Bake button now to create your new navigation mesh. Select the ramp and the thick cube that we created earlier. In the Navigation window, make sure that the OffMeshLink Generation option is not checked, and that Navigation Layer is set to Default. If the ramp and the thick cube are not selected, reselect the floor cubes so that you have the floors, ramp, and thick wall selected. Bake the navigation mesh again to create a new one. This is what my scene looks like now with the navigation mesh: You should be able to see the newly generated navigation mesh overlaying the underlying mesh. This is what was created using the default Bake properties. Changing the Bake properties will give you different results, which will come down to what kind of navigation mesh you want the AI to use. Now that we have a navigation mesh, let's create the code for our AI to utilize. First, we will code the waypoint system, and then we will code what is needed for the NavMesh system. Adding our variables To start our navigation system, we will need to add a few variables first. Place these with the rest of our variables: public Transform[] Waypoints;public int curWaypoint = 0;bool ReversePath = false;NavMeshAgent navAgent;Vector3 Destination;float Distance; The first variable is an array of Transforms; this is what we will use to hold our waypoints. Next, we have an integer that is used to iterate through our Transform array. We have a bool variable, which will decide how we should navigate through the waypoints. The next three variables are more oriented towards our navigation mesh that we created earlier. The NavMeshAgent object is what we will reference when we want to interact with the navigation mesh. The destination will be the location that we want the AI to move towards. The distance is what we will use to check how far away we are from that location. Scripting the navigation functions Previously, we created many empty functions; some of these are dependent on pathfinding. Let's start with the Flee function. Add this code to replace the empty function: void Flee(){for(int fleePoint = 0; fleePoint < Waypoints.Length; fleePoint++){   Distance = Vector3.Distance(gameObject.transform.position, Waypoints[fleePoint].position);   if(Distance > 10.00f)   {     Destination = Waypoints[curWaypoint].position;     navAgent.SetDestination(Destination);     break;   }   else if(Distance < 2.00f)   {     ChangeBehavior(Behaviors.Idle);   }}} What this for loop does is pick a waypoint that has Distance of more than 10. If it does, then we set the Destination value to the current waypoint and move the AI accordingly. If the distance from the current waypoint is less than 2, we change the behavior to Idle. The next function that we will adjust is the SearchForTarget function. Add the following code to it, replacing its previous emptiness: void SearchForTarget(){Destination = GameObject.FindGameObjectWithTag("Player").transform.position;navAgent.SetDestination(Destination);Distance = Vector3.Distance(gameObject.transform.position, Destination);if(Distance < 10)   ChangeBehavior(Behaviors.Combat);} This function will now be able to search for a target, the Player target to be more specific. We set Destination to the player's current position, and then move the AI towards the player. When Distance is less than 10, we set the AI behavior to Combat. Now that our AI can run from the player as well as chase them down, let's utilize the waypoints and create paths for the AI. Add this code to the empty Patrol function: void Patrol(){Distance = Vector3.Distance(gameObject.transform.position, Waypoints[curWaypoint].position);if(Distance > 2.00f){   Destination = Waypoints[curWaypoint].position;   navAgent.SetDestination(Destination);}else{   if(ReversePath)   {     if(curWaypoint <= 0)     {       ReversePath = false;     }     else     {       curWaypoint--;       Destination = Waypoints[curWaypoint].position;     }   }   else   {     if(curWaypoint >= Waypoints.Length - 1)     {       ReversePath = true;     }     else     {       curWaypoint++;       Destination = Waypoints[curWaypoint].position;     }   }}} What Patrol will now do is check the Distance variable. If it is far from the current waypoint, we set that waypoint as the new destination of our AI. If the current waypoint is close to the AI, we check the ReversePath Boolean variable. When ReversePath is true, we tell the AI to go to the previous waypoint, going through the path in the reverse order. When ReversePath is false, the AI will go on to the next waypoint in the list of waypoints. With all of this completed, you now have an AI with pathfinding abilities. The AI can also patrol a path set by waypoints and reverse the path when the end has been reached. We have also added abilities for the AI to search for the player as well as flee from the player. Character animations Animations are what bring the characters to life visually in the game. From basic animations to super realistic movements, all the animations are important and really represent what scripters do to the player. Before we add animations to our AI, we first need to get a model mesh for it! Importing the model mesh For this article, I am using a model mesh that I got from the Unity Asset Store. To use the same model mesh that I am using, go to the Unity Asset Store and search for Skeletons Pack. It is a package of four skeleton model meshes that are fully textured, propped, and animated. The asset itself is free and great to use. When you import the package into Unity, it will come with all four models as well as their textures, and an example scene named ShowCase. Open that scene and you should see the four skeletons. If you run the scene, you will see all the skeletons playing their idle animations. Choose the skeleton you want to use for your AI; I chose skeletonDark for mine. Click on the drop-down list of your skeleton in the Hierarchy window, and then on the Bip01 drop-down list. Then, select the magicParticle object. For our AI, we will not need it, so delete it from the Hierarchy window. Create a new prefab in the Project window and name it Skeleton. Now select the skeleton that you want to use from the Hierarchy window and drag it onto the newly created prefab. This will now be the model that you will use for this article. In your AI test scene, drag and drop Skeleton Prefab onto the scene. I have placed mine towards the center of the level, near the waypoint in the middle. In the Inspector window, you will be able to see the Animation component full of animations for the model. Now, we will need to add a few components to our skeleton. Go to the Components menu on the top of the Unity window, select Navigation, and then select NavMesh Agent. Doing this will allow the skeleton to utilize the NavMesh we created earlier. Next, go into the Components menu again and click on Capsule Collider as well as Rigidbody. Your Inspector window should now look like this after adding the components: Your model now has all the necessary components needed to work with our AI script. Scripting the animations To script our animations, we will take a simple approach to it. There won't be a lot of code to deal with, but we will spread it out in various areas of our script where we need to play the animations. In the Idle function, add this line of code: animation.Play("idle"); This simple line of code will play the idle animation. We use animation to access the model's animation component, and then use the Play function of that component to play the animation. The Play function can take the name of the animation to call the correct animation to be played; for this one, we call the idle animation. In the SearchForTarget function, add this line of code to the script: animation.Play("run"); We access the same function of the animation component and call the run animation to play here. Add the same line of code to the Patrol function as well, since we will want to use that animation for that function too. In the RangedAttack and MeleeAttack functions, add this code: animation.Play("attack"); Here, we call the attack animation. If we had a separate animation for ranged attacks, we would use that instead, but since we don't, we will utilize the same animation for both attack types. With this, we finished coding the animations into our AI. It will now play those animations when they are called during gameplay. Putting it all together To wrap up our AI package, we will now finish up the script and add it to the skeleton. Final coding touches At the beginning of our AI script, we created some variables that we have yet to properly assign. We will do that in the Start function. We will also add the Update function to run our AI code. Add these functions to the bottom of the class: void Start(){navAgent = GetComponent<NavMeshAgent>(); Stats.Add(new KeyValuePair<string, int>("Health", 100));Stats.Add(new KeyValuePair<string, int>("Speed", 10));Stats.Add(new KeyValuePair<string, int>("Damage", 25));Stats.Add(new KeyValuePair<string, int>("Agility", 25));Stats.Add(new KeyValuePair<string, int>("Accuracy", 60));} void Update (){RunBehaviors();} In the Start function, we first assign the navAgent variable by getting the NavMeshAgent component from the GameObject. Next, we add new KeyValuePair variables to the Stats array. The Stats array is now filled with a few stats that we created. The Update function calls the RunBehaviors function. This is what will keep the AI running; it will run the correct behavior as long as the AI is active. Filling out the inspector To complete the AI package, we will need to add the script to the skeleton, so drag the script onto the skeleton in the Hierarchy window. In the Size property of the waypoints array, type the number 5 and open up the drop-down list. Starting with Element 0, drag each of the waypoints into the empty slots. For the projectile, create a sphere GameObject and make it a prefab. Now, drag it onto the empty slot next to Projectile. Finally, set the AI Behaviors to Guard. This will make it so that when you start the scene, your AI will be patrolling. The Inspector window of the skeleton should look something like this: Your AI is now ready for gameplay! To make sure everything works, we will need to do some playtesting. Playtesting A great way to playtest the AI is to play the scene in every behavior. Start off with Guard, then run it in Idle, Combat, and Flee. For different outputs, try adjusting some of the variables in the NavMesh Agent component, such as Speed, Angular Speed, and Stopping Distance. Try mixing your waypoints around so the path is different. Summary In this article, you learned how to create an AI package. We explored a couple of techniques to handle AI, such as finite state machines and behavior trees. Then, we dived into AI actions, both internal and external. From there, we figured out how to implement pathfinding with both a waypoint system and Unity's NavMesh system. Finally, we topped the AI package off with animations and put everything together, creating our finalized AI. Resources for Article: Further resources on this subject: Getting Started – An Introduction to GML [article] Animations in Cocos2d-x [article] Components in Unity [article]
Read more
  • 0
  • 0
  • 10220

article-image-materials-ogre-3d
Packt
25 Nov 2010
7 min read
Save for later

Materials with Ogre 3D

Packt
25 Nov 2010
7 min read
OGRE 3D 1.7 Beginner's Guide Create real time 3D applications using OGRE 3D from scratch Easy-to-follow introduction to OGRE 3D Create exciting 3D applications using OGRE 3D Create your own scenes and monsters, play with the lights and shadows, and learn to use plugins Get challenged to be creative and make fun and addictive games on your own A hands-on do-it-yourself approach with over 100 examples Creating a white quad We will use this to create a sample quad that we can experiment with. Time for action – creating the quad We will start with an empty application and insert the code for our quad into the createScene() function: Begin with creating the manual object: Ogre::ManualObject* manual = mSceneMgr- >createManualObject("Quad"); manual->begin("BaseWhiteNoLighting", RenderOperation::OT_TRIANGLE_ LIST); Create four points for our quad: manual->position(5.0, 0.0, 0.0); manual->textureCoord(0,1); manual->position(-5.0, 10.0, 0.0); manual->textureCoord(1,0); manual->position(-5.0, 0.0, 0.0); manual->textureCoord(1,1); manual->position(5.0, 10.0, 0.0);manual->textureCoord(0,0); Use indices to describe the quad: manual->index(0); manual->index(1); manual->index(2); manual->index(0); manual->index(3); manual->index(1); Finish the manual object and convert it to a mesh: manual->end(); manual->convertToMesh("Quad"); Create an instance of the entity and attach it to the scene using a scene node: Ogre::Entity * ent = mSceneMgr->createEntity("Quad"); Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()- >createChildSceneNode("Node1"); node->attachObject(ent); Compile and run the application. You should see a white quad. What just happened? We used our knowledge to create a quad and attach to it a material that simply renders everything in white. The next step is to create our own material. Creating our own material Always rendering everything in white isn't exactly exciting, so let's create our first material. Time for action – creating a material Now, we are going to create our own material using the white quad we created. Change the material name in the application from BaseWhiteNoLighting to MyMaterial1: manual->begin("MyMaterial1", RenderOperation::OT_TRIANGLE_LIST); Create a new file named Ogre3DBeginnersGuide.material in the mediamaterialsscripts folder of our Ogre3D SDK. Write the following code into the material file: material MyMaterial1 { technique { pass { texture_unit { texture leaf.png } } } } Compile and run the application. You should see a white quad with a plant drawn onto it. What just happened? We created our first material file. In Ogre 3D, materials can be defined in material files. To be able to find our material files, we need to put them in a directory listed in the resources.cfg, like the one we used. We also could give the path to the file directly in code using the ResourceManager. To use our material defined in the material file, we just had to use the name during the begin call of the manual object. The interesting part is the material file itself. Materials Each material starts with the keyword material, the name of the material, and then an open curly bracket. To end the material, use a closed curly bracket—this technique should be very familiar to you by now. Each material consists of one or more techniques; a technique describes a way to achieve the desired effect. Because there are a lot of different graphic cards with different capabilities, we can define several techniques and Ogre 3D goes from top to bottom and selects the first technique that is supported by the user's graphic cards. Inside a technique, we can have several passes. A pass is a single rendering of your geometry. For most of the materials we are going to create, we only need one pass. However, some more complex materials might need two or three passes, so Ogre 3D enables us to define several passes per technique. In this pass, we only define a texture unit. A texture unit defines one texture and its properties. This time the only property we define is the texture to be used. We use leaf.png as the image used for our texture. This texture comes with the SDK and is in a folder that gets indexed by resources.cfg, so we can use it without any work from our side. Have a go hero – creating another material Create a new material called MyMaterial2 that uses Water02.jpg as an image. Texture coordinates take two There are different strategies used when texture coordinates are outside the 0 to 1 range. Now, let's create some materials to see them in action. Time for action – preparing our quad We are going to use the quad from the previous example with the leaf texture material: Change the texture coordinates of the quad from range 0 to 1 to 0 to 2. The quad code should then look like this: manual->position(5.0, 0.0, 0.0); manual->textureCoord(0,2); manual->position(-5.0, 10.0, 0.0); manual->textureCoord(2,0); manual->position(-5.0, 0.0, 0.0); manual->textureCoord(2,2); manual->position(5.0, 10.0, 0.0); manual->textureCoord(0,0); Now compile and run the application. Just as before, we will see a quad with a leaf texture, but this time we will see the texture four times. What just happened? We simply changed our quad to have texture coordinates that range from zero to two. This means that Ogre 3D needs to use one of its strategies to render texture coordinates that are larger than 1. The default mode is wrap. This means each value over 1 is wrapped to be between zero and one. The following is a diagram showing this effect and how the texture coordinates are wrapped. Outside the corners, we see the original texture coordinates and inside the corners, we see the value after the wrapping. Also for better understanding, we see the four texture repetitions with their implicit texture coordinates. We have seen how our texture gets wrapped using the default texture wrapping mode. Our plant texture shows the effect pretty well, but it doesn't show the usefulness of this technique. Let's use another texture to see the benefits of the wrapping mode. Using the wrapping mode with another texture Time for action – adding a rock texture For this example, we are going to use another texture. Otherwise, we wouldn't see the effect of this texture mode: Create a new material similar to the previous one, except change the used texture to: terr_rock6.jpg: material MyMaterial3 { technique { pass { texture_unit { texture terr_rock6.jpg } } } } Change the used material from MyMaterial1 to MyMaterial3: manual->begin("MyMaterial3", RenderOperation::OT_TRIANGLE_LIST) Compile and run the application. You should see a quad covered in a rock texture. What just happened? This time, the quad seems like it's covered in one single texture. We don't see any obvious repetitions like we did with the plant texture. The reason for this is that, like we already know, the texture wrapping mode repeats. The texture was created in such a way that at the left end of the texture, the texture is started again with its right side and the same is true for the lower end. This kind of texture is called seamless. The texture we used was prepared so that the left and right side fit perfectly together. The same goes for the upper and lower part of the texture. If this wasn't the case, we would see instances where the texture is repeated.
Read more
  • 0
  • 0
  • 10092

article-image-making-entity-multiplayer-ready
Packt
07 Apr 2014
8 min read
Save for later

Making an entity multiplayer-ready

Packt
07 Apr 2014
8 min read
(For more resources related to this topic, see here.) Understanding the dataflow of Lua entities in a multiplayer environment When using your own Lua entities in a multiplayer environment, you need to make sure everything your entity does on one of the clients is also triggered on all other clients. Let's take a light switch as an example. If one of the players turned on the light switch, the switch should also be flipped on all other clients. Each client connected to the game has an instance of that light switch in their level. The CryENGINE network implementation already handles all the work involved in linking these individual instances together using network entity IDs. Each light switch can contact its own instances on all connected clients and call its functions over the network. All you need to do is use the functionality that is already there. One way of implementing the light switch functionality is to turn on the switch in the entity as soon as the OnUsed() event is triggered and then send a message to all other clients in the network to also turn on their lights. This might work for something as simple as a switch, but can soon get messy when the entity becomes more complex. Ping times and message orders can lead to inconsistencies if two players try to flip the light switch at the same time. The representation of the process would look like the following diagram: Not so good – the light switch entity could trigger its own switch on all network instances of itself Doing it this way, with the clients notifying each other, can cause many problems. In a more stable solution, these kinds of events are usually run through the server. The server entity—let's call it the master entity—determines the state of the entities across the network at all times and distributes the entities throughout the network. This could be visualized as shown in the following diagram: Better – the light switch entity calls the server that will distribute the event to all clients In the light switch scenario mentioned earlier, the light switch entity would send an event to the server light switch entity first. Then, the server entity would call each light switch entity, including the original sender, to turn on their lights. It is important to understand that the entity that received the event originally does nothing else but inform the server about the event. The actual light is not turned on until the server calls back to all entities with the request to do so. The aforementioned dataflow works in single player as well, as CryENGINE will just pretend that the local machine is both the client and the server. This way, you will not have to make adjustments or add extra code to your entity to check whether it is single player or multiplayer. In a multiplayer environment with a server and multiple clients, it is important to set the script up so that it acts properly and the correct functions are called on either the client or the server. The first step to achieve this is to add a client and server table to the entity script using the following code: Client = {}, Server = {}, With this addition, our script table looks like the following code snippet: Testy = {Properties={ fileModel = "",Physics = { bRigidBody=1, = 1, Density = -1, Mass = -1, }, Client = {}, Server = {}, Editor={ Icon="User.bmp", }, } Now, we can go ahead and modify the functions so that they work properly in multiplayer. We do this by adding the Client and Server subtables to our script. This way, the network system will be able to identify the Client/Server functions on the entity. The Client/Server functions The Client/Server functions are defined within your entity script by using the respective subtables that we previously defined in the entity table. Let's update our script and add a simple function that outputs a debug text into the console on each client. In order for everything to work properly, we first need to update our OnInit() function and make sure it gets called on the server properly. Simply add a server subtable to the function so that it looks like the following code snippet: functionTesty.Server:OnInit() self:OnReset(); end; This way, our OnReset() function will still be called properly. Now, we can add a new function that outputs a debug text for us. Let's keep it simple and just make it output a console log using the CryENGINE Log function, as shown in the following code snippet: functionTesty.Client:PrintLogOutput(text) Log(text); end; This function will simply print some text into the CryENGINE console. Of course, you can add more sophisticated code at this point to be executed on the client. Please also note the Client subtable in the function definition that tells the engine that this is a client function. In the next step, we have to add a way to trigger this function so that we can test the behavior properly. There are many ways of doing this, but to keep things simple, we will simply use the OnHit() callback function that will be automatically triggered when the entity is hit by something; for example, a bullet. This way, we can test our script easily by just shooting at our entity. The OnHit() callback function is quite simple. All it needs to do in our case is to call our PrintLogOutput function, or rather request the server to call it. For this purpose, we add another function to be called on the server that calls our PrintLogOutput() function. Again, please note that we are using the Client subtable of the entity to catch the hit that happens on the client. Our two new functions should look as shown in the following code snippet: functionTesty.Client:OnHit(user) self.server:SvRequestLogOutput("My Text!"); end functionTesty.Server:SvRequestLogOutput(text) self.allClients:PrintLogOutput(text); end We now have two new functions: one is a client function calling a server function and the other one is a server function calling the actual function on all the clients. The Remote Method Invocation definitions As a last step, before we are finished, we need to expose our entity and its functions to the network. We can do this by adding a table within the root of our entity script that defines the necessary Remote Method Invocation (RMI). The Net.Expose table will expose our entity and its functions to the network so that they can be called remotely, as shown in the following code snippet: Net.Expose { Class = Testy, ClientMethods = { PrintLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING }, }, ServerMethods = { SvRequestLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING}, }, ServerProperties = { }, }; Each RMI is defined by providing a function name, a set of RMI flags, and additional parameters. The first RMI flag is an order flag and defines the order of the network packets. You can choose between the following options: UNRELIABLE_ORDERED RELIABLE_ORDERED RELIABLE_UNORDERED These flags tell the engine whether the order of the packets is important or not. The attachment flag will define at what time the RMI is attached during the serialization process of the network. This parameter can be either of the following flags: PREATTACH: This flag attaches the RMI before game data serialization. POSTATTACH: This flag attaches the RMI after game data serialization. NOATTACH: This flag is used when it is not important if the RMI is attached before or after the game data serialization. FAST: This flag performs an immediate transfer of the RMI without waiting for a frame update. This flag is very CPU intensive and should be avoided if possible. The Net.Expose table we just added defines which functions will be exposed on the client and the server and will give us access to the following three subtables: allClients otherClients server With these functions, we can now call functions either on the server or the clients. You can use the allClients subtable to call a function on all clients or the otherClients subtable to call it on all clients except the own client. At this point, the entity table of our script should look as follows: Testy = { Properties={ fileModel = "", Physics = { bRigidBody=1, bRigidBodyActive = 1, Density = -1, Mass = -1, }, Client = {}, Server = {}, Editor={ Icon="User.bmp", ShowBounds = 1, }, } Net.Expose { Class = Testy, ClientMethods = { PrintLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING }, }, ServerMethods = { SvRequestLogOutput = { RELIABLE_UNORDERED, POST_ATTACH, STRING}, }, ServerProperties = { }, }; This defines our entity and its network exposure. With our latest updates, the rest of our script with all its functions should look as follows: functionTesty.Server:OnInit() self:OnReset(); end; functionTesty:OnReset() local props=self.Properties; if(not EmptyString(props.fileModel))then self:LoadObject(0,props.fileModel); end; EntityCommon.PhysicalizeRigid(self,0,props.Physics,0); self:DrawSlot(0, 1); end; functionTesty:OnPropertyChange() self:OnReset(); end; functionTesty.Client:PrintLogOutput(text) Log(text); end; functionTesty.Client:OnHit(user) self.server:SvRequestLogOutput("My Text!"); end functionTesty.Server:SvRequestLogOutput(text) self.allClients:PrintLogOutput(text); end With these functions added to our entity, everything should be ready to go and you can test the behavior in game mode. When the entity is being shot at, the OnHit() function will request the log output to be printed from the server. The server calls the actual function on all clients. Summary In this article we learned about making our entity ready for a multiplayer environment by understanding the dataflow of Lua entities, understanding the Client/Server functions, and by exposing our entities to the network using the Remote Method Invocation definitions. Resources for Article: Further resources on this subject: CryENGINE 3: Terrain Sculpting [Article] CryENGINE 3: Breaking Ground with Sandbox [Article] CryENGINE 3: Fun Physics [Article]
Read more
  • 0
  • 0
  • 10074
article-image-animating-panda3d
Packt
01 Mar 2011
8 min read
Save for later

Animating in Panda3D

Packt
01 Mar 2011
8 min read
Panda3D 1.6 Game Engine Beginner's Guide Create your own computer game with this 3D rendering and game development framework The first and only guide to building a finished game using Panda3D Learn about tasks that can be used to handle changes over time Respond to events like keyboard key presses, mouse clicks, and more Take advantage of Panda3D's built-in shaders and filters to decorate objects with gloss, glow, and bump effects Follow a step-by-step, tutorial-focused process that matches the development process of the game with plenty of screenshots and thoroughly explained code for easy pick up        Actors and Animations An Actor is a kind of object in Panda3D that adds more functionality to a static model. Actors can include joints within them. These joints have parts of the model tied to them and are rotated and repositioned by animations to make the model move and change. Actors are stored in .egg and .bam files, just like models. Animation files include information on the position and rotation of joints at specific frames in the animation. They tell the Actor how to posture itself over the course of the animation. These files are also stored in .egg and .bam files. Time for action – loading Actors and Animations Let's load up an Actor with an animation and start it playing to get a feel for how this works: Open a blank document in NotePad++ and save it as Anim_01.py in the Chapter09 folder. We need a few imports to start with. Put these lines at the top of the file: import direct.directbase.DirectStart from pandac.PandaModules import * from direct.actor.Actor import Actor We won't need a lot of code for our class' __init__ method so let's just plow through it here : class World: def __init__(self): base.disableMouse() base.camera.setPos(0, -5, 1) self.setupLight() self.kid = Actor("../Models/Kid.egg", {"Walk" : "../Animations/Walk.egg"}) self.kid.reparentTo(render) self.kid.loop("Walk") self.kid.setH(180) The next thing we want to do is steal our setupLight() method from the Track class and paste it into this class: def setupLight(self): primeL = DirectionalLight("prime") primeL.setColor(VBase4(.6,.6,.6,1)) self.dirLight = render.attachNewNode(primeL) self.dirLight.setHpr(45,-60,0) render.setLight(self.dirLight) ambL = AmbientLight("amb") ambL.setColor(VBase4(.2,.2,.2,1)) self.ambLight = render.attachNewNode(ambL) render.setLight(self.ambLight) return Lastly, we need to instantiate the World class and call the run() method. w = World() run() Save the file and run it from the command prompt to see our loaded model with an animation playing on it, as depicted in the following screenshot: What just happened? Now, we have an animated Actor in our scene, slowly looping through a walk animation. We made that happen with only three lines of code: self.kid = Actor("../Models/Kid.egg", {"Walk" : "../Animations/Walk.egg"}) self.kid.reparentTo(render) self.kid.loop("Walk") The first line creates an instance of the Actor class. Unlike with models, we don't need to use a method of loader. The Actor class constructor takes two arguments: the first is the filename for the model that will be loaded. This file may or may not contain animations in it. The second argument is for loading additional animations from separate files. It's a dictionary of animation names and the files that they are contained in. The names in the dictionary don't need to correspond to anything; they can be any string. myActor = Actor( modelPath, {NameForAnim1 : Anim1Path, NameForAnim2 : Anim2Path, etc}) The names we give animations when the Actor is created are important because we use those names to control the animations. For instance, the last line calls the method loop() with the name of the walking animation as its argument. If the reference to the Actor is removed, the animations will be lost. Make sure not to remove the reference to the Actor until both the Actor and its animations are no longer needed. Controlling animations Since we're talking about the loop() method, let's start discussing some of the different controls for playing and stopping animations. There are four basic methods we can use: play("AnimName"): This method plays the animation once from beginning to end. loop("AnimName"): This method is similar to play, but the animation doesn't stop when it's over; it replays again from the beginning. stop() or stop("AnimName"): This method, if called without an argument, stops all the animations currently playing on the Actor. If called with an argument, it only stops the named animation. Note that the Actor will remain in whatever posture they are in when this method is called. pose("AnimName", FrameNumber): Places the Actor in the pose dictated by the supplied frame without playing the animation. We have some more advanced options as well. Firstly, we can provide option fromFrame and toFrame arguments to play or loop to restrict the animation to specific frames. myActor.play("AnimName", fromFrame = FromFrame, toFrame = toFrame) We can provide both the arguments, or just one of them. For the loop() method, there is also the optional argument restart, which can be set to 0 or 1. It defaults to 1, which means to restart the animation from the beginning. If given a 0, it will start looping from the current frame. We can also use the getNumFrames("AnimName") and getCurrentFrame("AnimName") methods to get more information about a given animation. The getCurrentAnim() method will return a string that tells us which animation is currently playing on the Actor. The final method we have in our list of basic animation controls sets the speed of the animation. myActor.setPlayRate(1.5, "AnimName") The setPlayRate() method takes two arguments. The first is the new play rate, and it should be expressed as a multiplier of the original frame rate. If we feed in .5, the animation will play half as fast. If we feed in 2, the animation will play twice as fast. If we feed in -1, the animation will play at its normal speed, but it will play in reverse. Have a go hero – basic animation controls Experiment with the various animation control methods we've discussed to get a feel for how they work. Load the Stand and Thoughtful animations from the animations folder as well, and use player input or delayed tasks to switch between animations and change frame rates. Once we're comfortable with what we've gone over so far, we'll move on. Animation blending Actors aren't limited to playing a single animation at a time. Panda3D is advanced enough to offer us a very handy functionality, called blending. To explain blending, it's important to understand that an animation is really a series of offsets to the basic pose of the model. They aren't absolutes; they are changes from the original. With blending turned on, Panda3D can combine these offsets. Time for action – blending two animations We'll blend two animations together to see how this works. Open Anim_01.py in the Chapter09 folder. We need to load a second animation to be able to blend. Change the line where we create our Actor to look like the following code: self.kid = Actor("../Models/Kid.egg", {"Walk" : "../Animations/Walk.egg", "Thoughtful" : "../Animations/Thoughtful.egg"}) Now, we just need to add this code above the line where we start looping the Walk animation: self.kid.enableBlend() self.kid.setControlEffect("Walk", 1) self.kid.setControlEffect("Thoughtful", 1) Resave the file as Anim_02.py and run it from the command prompt. What just happened? Our Actor is now performing both animations to their full extent at the same time. This is possible because we made the call to the self.kid.enableBlend() method and then set the amount of effect each animation would have on the model with the self.kid.setControlEffect() method. We can turn off blending later on by using the self.kid.disableBlend() method, which will return the Actor to the state where playing or looping a new animation will stop any previous animations. Using the setControlEffect method, we can alter how much each animation controls the model. The numeric argument we pass to setControlEffect() represents a percentage of the animation's offset that will be applied, with 1 being 100%, 0.5 being 50%, and so on. When blending animations together, the look of the final result depends a great deal on the model and animations being used. Much of the work needed to achieve a good result depends on the artist. Blending works well for transitioning between animations. In this case, it can be handy to use Tasks to dynamically alter the effect animations have on the model over time. Honestly, though, the result we got with blending is pretty unpleasant. Our model is hardly walking at all, and he looks like he has a nervous twitch or something. This is because both animations are affecting the entire model at full strength, so the Walk and Thoughtful animations are fighting for control over the arms, legs, and everything else, and what we end up with is a combination of both animation's offsets. Furthermore, it's important to understand that when blending is enabled, every animation with a control effect higher than 0 will always be affecting the model, even if the animation isn't currently playing. The only way to remove an animation's influence is to set the control effect to 0. This obviously can cause problems when we want to play an animation that moves the character's legs and another animation that moves his arms at the same time, without having them screw with each other. For that, we have to use subparts.  
Read more
  • 0
  • 0
  • 10043

article-image-managing-and-displaying-information
Packt
17 Sep 2013
37 min read
Save for later

Managing and Displaying Information

Packt
17 Sep 2013
37 min read
(For more resources related to this topic, see here.) In order to realize these goals, in this article, we'll be doing the following: Displaying a countdown timer on the screen Configuring fonts Creating a game attribute to count lives Using graphics to display information Counting collected actors Keeping track of the levels Prior to continuing with the development of our game, let's take a little time out to review what we have achieved so far, and also to consider some of the features that our game will need before it can be published. A review of our progress The gameplay mechanics are now complete; we have a controllable character in the form of a monkey, and we have some platforms for the monkey to jump on and traverse the scene. We have also introduced some enemy actors, the croc and the snake, and we have Aztec statues falling from the sky to create obstacles for the monkey. Finally, we have the fruit, all of which must be collected by the monkey in order to successfully complete the level. With regards to the scoring elements of the game, we're currently keeping track of a countdown timer (displayed in the debug console), which causes the scene to completely restart when the monkey runs out of time. When the monkey collides with an enemy actor, the scene is not reloaded, but the monkey is sent back to its starting point in the scene, and the timer continues to countdown. Planning ahead – what else does our game need? With the gameplay mechanics working, we need to consider what our players will expect to happen when they have completed the task of collecting all the fruits. As mentioned in the introduction to this article, our plan is to create additional, more difficult levels for the player to complete! We also need to consider what will happen when the game is over; either when the player has succeeded in collecting all the fruits, or when the player has failed to collect the fruits in the allocated time. The solution that we'll be implementing in this game is to display a message to advise the player of their success or failure, and to provide options for the player to either return to the main menu, or if the task was completed successfully, continue to the next level within the game. We need to implement a structure so that the game can keep track of information, such as how many lives the player has left and which level of the game is currently being played. Let's put some information on the screen so that our players can keep track of the countdown timer. Displaying a countdown timer on the screen We created a new scene behavior called Score Management, which contains the Decrement Countdown event, shown as follows: Currently, as we can see in the previous screenshot, this event decrements the Countdown attribute by a value of 1, every second. We also have a debug print instruction that displays the current value of Countdown in the debug console to help us, as game developers, keep track of the countdown. However, players of the game cannot see the debug console, so we need to provide an alternative means of displaying the amount of time that the player has to complete the level. Let's see how we can display that information on the screen for players of our game. Time for action – displaying the countdown timer on the screen Ensure that the Score Management scene behavior is visible: click on the Dashboard tab, select Scene Behaviors, and double-click on the Score Management icon in the main panel. Click + Add Event | Basics | When Drawing. Double-click on the title of the new Drawing event, and rename it to Display Countdown. Click on the Drawing section button in the instruction block palette. Drag a draw text anything at (x: 0 y: 0) block into the orange when drawing event block in the main panel. Enter the number 10 into the x: textbox and also enter 10 into the y: textbox. Click on the drop-down arrow in the textbox after draw text and select Text | Basics. Then click on the text & text block. In the first textbox in green, … & … block, enter the text COUNTDOWN: (all uppercase, followed by a colon). In the second textbox, after the & symbol, click on the drop-down arrow and select Basics, then click on the anything as text block. Click on the drop-down arrow in the … as text block, and select Number | Attributes | Countdown. Ensure that the new Display Countdown event looks like the following screenshot: Test the game. What just happened? When the game is played, we can now see in the upper-left corner of the screen, a countdown timer that represents the value of the Countdown attribute as it is decremented each second. First, we created a new Drawing event, which we renamed to Display Countdown, and then we added a draw text anything at (x: 0 y: 0) block, which is used to display the specified text in the required location on the screen. We set both the x: and y: coordinates for displaying the drawn text to 10 pixels, that is, 10 pixels from the left-hand side of the screen, and 10 pixels from the top of the screen. The next task was to add some text blocks that enabled us to display an appropriate message along with the value of the Countdown attribute. The text & text block enables us to concatenate, or join together, two separate pieces of text. The Countdown attribute is a number, so we used the anything as text block to convert the value of the Countdown attribute to text to ensure that it will be displayed correctly when the game is being played. In practice, we could have just located the Countdown attribute block in the Attributes section of the palette, and then dragged it straight into the text & text block. However, it is best practice to correctly convert attributes to the appropriate type, as required by the instruction block. In our case, the number attribute is being converted to text because it is being used in the text concatenation instruction block. If we needed to use a text value in a calculation, we would convert it to a number using an anything as number block. Configuring fonts We can see, when testing the game, that the font we have used is not very interesting; it's a basic font that doesn't really suit the style of the game! Stencyl allows us to specify our own fonts, so our next step is to import a font to use in our game. Time for action – specifying a font for use in our game Before proceeding with the following steps, we need to locate the fonts-of-afrikaAfritubu.TTF file. Place the file in a location where it can easily be located, and continue with the following steps: In the Dashboard tab, click on Fonts. In the main panel, click on the box containing the words This game contains no Fonts. Click here to create one. In the Name textbox of the Create New… dialog box, type HUD Font and click on the Create button. In the left-hand panel, click on the Choose… button next to the Font selector. Locate the file Afritubu.TTF and double-click on it to open it. Note that the main panel shows a sample of the new font. In the left-hand panel, change the Size option to 25. Important: save the game! Return to the Display Countdown event in the Score Management scene behavior. In the instruction block palette, click on the Drawing section button and then the Styles category button. Drag the set current font to Font block above the draw text block in the when drawing event. Click on the Font option in the set current font to Font block, and select Choose Font from the pop-up menu. Double-click on the HUD Font icon in the Choose a Font… dialog box. Test the game. Observe the countdown timer at the upper-left corner of the game. What just happened? We can see that the countdown timer is now being displayed using the new font that we have imported into Stencyl, as shown in the following screenshot: The first step was to create a new blank font in the Stencyl dashboard and to give it a name (we chose HUD Font), and then we imported the font file from a folder on our hard disk. Once we had imported the font file, we could see a sample of the font in the main panel. We then increased the size of the font using the Size option in the left-hand panel. That's all we needed to do in order to import and configure a new font in Stencyl! However, before progressing, we saved the game to ensure that the newly imported font will be available for the next steps. With our new font ready to use, we needed to apply it to our countdown text in the Display Countdown behavior. So, we opened up the behavior and inserted the set current font to Font style block. The final step was to specify which font we wanted to use, by clicking on the Font option in the font style block, and choosing the new font, HUD Font, which we configured in the earlier steps. Heads-Up Display (HUD) is often used in games to describe either text or graphics that is overlaid on the main game graphics to provide the player with useful information. Using font files in Stencyl Stencyl can use any TrueType font that we have available on our hard disk (files with the extension TTF); many thousands of fonts are available to download from the Internet free of charge, so it's usually possible to find a font that suits the style of any game that we might be developing. Fonts are often subject to copyright, so be careful to read any licensing agreements that are provided with the font file, and only download font files from reliable sources. Have a go hero When we imported the font into Stencyl, we specified a new font size of 25, but it is a straightforward process to modify further aspects of the font style, such as the color and other effects. Click on the HUD Font tab to view the font settings (or reopen the Font Editor from the Dashboards tab) and experiment with the font size, color, and other effects to find an appropriate style for the game. Take this opportunity to learn more about the different effects that are available, referring to Stencyl's online help if required. Remember to test the game to ensure that any changes are effective and the text is not difficult to read! Creating a game attribute to count lives Currently, our game never ends. As soon as the countdown reaches zero, the scene is restarted, or when the monkey collides with an enemy actor, the monkey is repositioned at the starting point in the scene. There is no way for our players to lose the game! In some genres of game, the player will never be completely eliminated; effectively, the same game is played forever. But in a platform game such as ours, the player typically will have a limited number of chances or lives to complete the required task. In order to resolve our problem of having a never-ending game, we need to keep track of the number of lives available to our player. So let's start to implement that feature right now by creating a game attribute called Lives! Time for action – creating a Lives game attribute Click on the Settings icon on the Stencyl toolbar at the top of the screen. In the left-hand panel of the Game Settings dialog box, click on the Attributes option. Click on the green Create New button. In the Name textbox, type Lives. In the Category textbox, change the word Default to Scoring. In the Type section, ensure that the currently selected option is Number. Change Initial Value to 3. Click on OK to confirm the configuration. We'll leave the Game Settings dialog box open, so that we can take a closer look. What just happened? We have created a new game attribute called Lives. If we look at the rightmost panel of the Game Settings dialog box that we left open on the screen, we can see that we have created a new heading entitled SCORING, and underneath the heading, there is a label icon entitled Lives, as shown in the following screenshot: The Lives item is a new game attribute that can store a number. The category name of SCORING that we created is not used within the game. We can't access it with the instruction blocks; it is there purely as a memory aid for the game developer when working with game attributes. When many game attributes are used in a game, it can become difficult to remember exactly what they are for, so being able to place them under specific headings can be helpful. Using game attributes The attributes we have used so far, such as the Countdown attribute that we created in the Score Management behavior, lose their values as soon as a different scene is loaded, or when the current scene is reloaded. Some game developers may refer to these attributes as local called Lives, so let's attributes, because they belong to the behavior in which they were created. Losing its value is fine when the attribute is just being used within the current scene; for example, we don't need to keep track of the countdown timer outside of the Jungle scene, because the countdown is reset each time the scene is loaded. However, sometimes we need to keep track of values across several scenes within a game, and this is when game attributes become very useful. Game attributes work in a very similar manner to local attributes. They store values that can be accessed and modified, but the main difference is that game attributes keep their values even when a different scene is loaded. Currently, the issue of losing attribute values when a scene is reloaded is not important to us, because our game only has one scene. However, when our players succeed in collecting all the fruits, we want the next level to be started without resetting the number of lives. So we need the number of lives to be remembered when the next scene is loaded. We've created a game attribute called Lives, so let's put it to good use. Time for action – decrementing the number of lives If the Game Settings dialog box is still open, click on OK to close it. Open the Manage Player Collisions actor behavior. Click on the Collides with Enemies event in the left-hand panel. Click on the Attributes section button in the palette. Click on the Game Attributes category button. Locate the purple set Lives to 0 block under the Number Setters subcategory and drag it into the orange when event so that it appears above the red trigger event RestartLevel in behavior Health for Self block. Click on the drop-down arrow in the set Lives to … block and select 0 - 0 in the Math section. In the left textbox of the … - … block, click on the drop-down arrow and select Game Attributes | Lives. In the right-hand textbox, enter the digit 1. Locate the print anything block in the Flow section of the palette, under the Debug category, and drag it below the set Lives to Lives – 1 block. In the print … block, click on the drop-down arrow and select Text | Basics | text & text. In the first empty textbox, type Lives remaining: (including the colon). Click on the drop-down arrow in the second textbox and select Basics | anything as text. In the … as text block, click on the drop-down arrow and select Number | Game Attributes | Lives. Ensure that the Collides with Enemies event looks like the following screenshot: Test the game; make the monkey collide with an enemy actor, such as the croc, and watch the debug console! What just happened? We have modified the Collides with Enemies event in the Manage Player Collisions behavior so that it decrements the number of lives by one when the monkey collides with an enemy actor, and the new value of Lives is shown in the debug console. This was achieved by using the purple game attribute setter and getter blocks to set the value of the Lives game attribute to its current value minus one. For example, if the value of Lives is 3 when the event occurs, Lives will be set to 3 minus 1, which is 2! The print … block was then used to display a message in the console, advising how many lives the player has remaining. We used the text & text block to join the text Lives remaining: together with the current value of the Lives game attribute. The anything as text block converts the numeric value of Lives to text to ensure that it will display correctly. Currently, the value of the Lives attribute will continue to decrease below 0, and the monkey will always be repositioned at its starting point. So our next task is to make something happen when the value of the Lives game attribute reaches 0! No more click-by-click steps! From this point onwards, click-by-click steps to modify behaviors and to locate and place each instruction block will not be specified! Instead, an overview of the steps will be provided, and a screenshot of the completed event will be shown towards the end of each Time for action section. The search facility, at the top of the instruction block palette, can be used to locate the required instruction block; simply click on the search box and type any part of the text that appears in the required block, then press the Enter key on the keyboard to display all the matching blocks in the block palette. Time for action – detecting when Lives reaches zero Create a new scene called Game Over — Select the Dashboard tab, select Scenes, and then select Click here to create a new Scene. Leave all the settings at their default configuration and click on OK. Close the tab for the newly created scene. Open the Manage Player Collisions behavior and click on the Collides with Enemies event to display the event's instruction blocks. Insert a new if block under the existing print block. Modify the if block to if Lives < 0. Move the existing block, trigger event RestartLevel in behavior Health for Self into the if Lives > 0 block. Insert an otherwise block below the if Lives > 0 block. Insert a switch to Scene and Crossfade for 0 secs block inside the otherwise block. Click on the Scene option in the new block, then click on Choose Scene and select the Game Over scene. Change the secs textbox to 0 (zero). Ensure that our modified Collides with Enemies event now looks like the following screenshot: Test the game; make the monkey run into an enemy actor, such as the croc, three times. What just happened? We have modified the Collides with Enemies event so that the value of the Lives game attribute is tested after it has been decremented, and the game will switch to the Game Over scene if the number of lives remaining is less than zero. If the value of Lives is greater than zero, the RestartLevel event in the monkey's Health behavior is triggered. However, if the value of Lives is not greater than zero, the instruction in the otherwise block will be executed, and this switches to the (currently blank) Game Over scene that we have created. If we review all the instructions in the completed Collides with Enemies event, and write them in English, the statement will be: When the monkey collides with an enemy, reduce the value of Lives by one and print the new value to the debug console. Then, if the value of Lives is more than zero, trigger the RestartLevel event in the monkey's Health behavior, otherwise switch to the Game Over scene. Before continuing, we should note that the Game Over scene has been created as a temporary measure to ensure that as we are in the process of developing the game, it's immediately clear to us (the developer) that the monkey has run out of lives. Have a go hero Change the Countdown attribute value to 30 — open the Jungle scene, click on the Behaviors button, then select the Score Management behavior in the left panel to see the attributes for this behavior. The following tasks in this Have a go hero session are optional — failure to attempt them will not affect future tutorials, but it is a great opportunity to put some of our newly learned skills to practice! In the section, Time for action – displaying the countdown timer on the screen, we learned how to display the value of the countdown timer on the screen during gameplay. Using the skills that we have acquired in this article, try to complete the following tasks: Update the Score Management behavior to display the number of lives at the upper-right corner of the screen, by adding some new instruction blocks to the Display Counter event. Rename the Display Counter event to Display HUD. Remove the print Countdown block from the Decrement Countdown event also found in the Score Management behavior. Right-click on the instruction block and review the options available in the pop-up menu! Remove the print Lives remaining: & Lives as text instruction block from the Collides with Enemies event in the Manage Player Collisions behavior. Removing debug instructions Why did we remove the debug print … blocks in the previous Have a go hero session? Originally, we added the debug blocks to assist us in monitoring the values of the Countdown attribute and Lives game attribute during the development process. Now that we have updated the game to display the required information on the screen, the debug blocks are redundant! While it would not necessarily cause a problem to leave the debug blocks where they are, it is best practice to remove any instruction blocks that are no longer in use. Also, during development, excessive use of debug print blocks can have an impact on the performance of the game; so it's a good idea to remove them as soon as is practical. Using graphics to display information We are currently displaying two on-screen pieces of information for players of our game: the countdown timer and the number of lives available. However, providing too much textual information for players can be distracting for them, so we need to find an alternative method of displaying some of the information that the player needs during gameplay. Rather than using text to advise the player how much time they have remaining to complete the level, we're going to display a timer bar on the screen. Time for action – displaying a timer bar Open the Score Management scene behavior and click on the Display HUD event. In the when drawing event, right-click on the blue block that draws the text for the countdown timer and select Activate / Deactivate from the pop-up menu. Note that the block becomes faded. Locate the draw rect at (x: 0 y: 0) with (w: 0 h: 0) instruction block in the palette, and insert it at the bottom of the when drawing event. Click on the draw option in the newly inserted block and change it to fill. Set both the x: and y: textboxes to 10. Set the width (w:) to Countdown x 10. Set the height (h:) to 10. Ensure that the draw text … block and the fill rect at … block in the Display HUD event appear as shown in the following screenshot (the draw text LIVES: … block may look different if the earlier Have a go hero section was attempted): Test the game! What just happened? We have created a timer bar that displays the amount of time remaining for the player to collect the fruit, and the timer bar reduces in size with the countdown! First, in the Display HUD event we deactivated, or disabled, the block that was drawing the textual countdown message, because we no longer want the text message to be displayed on the screen. The next step was to insert a draw rect … block that was configured to create a filled rectangle at the upper-left corner of the screen and with a width equal to the value of the Countdown timer multiplied by 10. If we had not multiplied the value of the countdown by 10, the timer bar would be very small and difficult to see (try it)! We'll be making some improvements to the timer bar later in this article. Activating and deactivating instruction blocks When we deactivate an instruction block, as we did in the Display HUD event, it no longer functions; it's completely ignored! However, the block remains in place, but is shown slightly faded, and if required, it can easily be reenabled by right-clicking on it and selecting the Activate / Deactivate option. Being able to activate and deactivate instruction blocks without deleting them is a useful feature — it enables us to try out new instructions, such as our timer bar, without having to completely remove blocks that we might want to use in the future. If, for example, we decided that we didn't want to use the timer bar, we could deactivate it and reactivate the draw text … block! Deactivated instruction blocks have no impact on the performance of a game; they are completely ignored during the game compilation process. Have a go hero The tasks in this Have a go hero session are optional; failure to attempt them will not affect future tutorials. Referring to Stencyl's online help if required at www.stencyl.com/help/, try to make the following improvements to the timer bar: Specify a more visually appealing color for the rectangle Make it thicker (height) so that it is easier to see when playing the game Consider drawing a black border (known as a stroke) around the rectangle Try to make the timer bar reduce in size smoothly, rather than in big steps Ask an independent tester for feedback about the changes and then modify the bar based on the feedback. To view suggested modifications together with comments, review the Display HUD event in the downloadable files that accompany this article. Counting collected actors With the number of lives being monitored and displayed for the player, and the timer bar in place, we now need to create some instructions that will enable our game to keep track of how many of the fruit actors have been collected, and to carry out the appropriate action when there is no fruit left to collect. Time for action – counting the fruit Open the Score Management scene behavior and create a new number attribute (not a game attribute) with the configuration shown in the following screenshot (in the block palette, click on Attributes, and then click on Create an Attribute…). Add a new when created event and rename it to Initialize Fruit Required. Add the required instruction blocks to the new when created event, so the Initialize Fruit Required event appears as shown in the following screenshot, carefully checking the numbers and text in each of the blocks' textboxes: Note that the red of group block in the set actor value … block cannot be found in the palette; it has been dragged into place from the orange for each … of group Collectibles block. Test the game and look for the Fruit required message in the debug console. What just happened? Before we can create the instructions to determine if all the fruit have been collected, we need to know how many fruit actors there are to collect. So we have created a new event that stores that information for us in a number attribute called Fruit Required and displays it in the debug console. We have created a for each … of group Collectibles block. This very useful looping block will repeat the instructions placed inside it for each member of the specified group that can be found in the current scene. We have specified the Collectibles group, and the instruction that we have placed inside the new loop is increment Fruit Required by 1. When the loop has completed, the value of the Fruit Required attribute is displayed in the debug console using a print … block. When constructing new events, it's good practice to insert print … blocks so we can be confident that the instructions achieve the results that we are expecting. When we are happy that the results are as expected, perhaps after carrying out further testing, we can remove the debug printing from our event. We have also introduced a new type of block that can set a value for an actor; in this case, we have set actor value Collected for … of group to false. This block ensures that each of the fruit actors has a value of Collected that is set to false each time the scene is loaded; remember that this instruction is inside the for each … loopso it is being carried out for every Collectible group member in the current scene. Where did the actor's Collected value come from? Well, we just invented it! The set actor value … block allows us to create an arbitrary value for an actor at any time. We can also retrieve that value at any time with a corresponding get actor value … block, and we'll be doing just that when we check to see if a fruit actor has been collected in the next section, Time for action – detecting when all the fruit are collected. Translating our instructions into English, results in the following statement: For each actor in the Collectibles group, that can be found in this scene, add the value 1 to the Fruit Required attribute and also set the actor's Collected value to false. Finally, print the result in the debug console. Note that the print … block has been placed after the for each … loop, so the message will not be printed for each fruit actor; it will appear just once, after the loop has completed! If we wish to prove to ourselves that the loop is counting correctly, we can edit the Jungle scene and add as many fruit actors as we wish. When we test the game, we can see that the number of fruit actors in the scene is correctly displayed in the debug console. We have designed a flexible set of instructions that can be used in any scene with any number of fruit actors, and which does not require us (as the game designer) to manually configure the number of fruit actors to be collected in that scene! Once again, we have made life easier for our future selves! Now that we have the attribute containing the number of fruit to be collected at the start of the scene, we can create the instructions that will respond when the player has successfully collected them all. Time for action – detecting when all fruits have been collected Create a new scene called Level Completed, with a Background Color of yellow. Leave all the other settings at their default configuration. Close the tab for the newly created scene. Return to the Score Management scene behavior, and create a new custom event by clicking on + Add Event | Advanced | Custom Event. In the left-hand panel, rename the custom event to Fruit Collected. Add the required instruction blocks to the new Fruit Collected event, so it appears as shown in the following screenshot, again carefully checking the parameters in each of the text boxes: Note that there is no space in the when FruitCollected happens custom event name. Save the game and open the Manage Player Collisions actor behavior. Modify the Collides with Collectibles event so it appears as shown in the following screenshot. The changes are listed in the subsequent steps: A new if get actor value Collected for … of group = false block has been inserted. The existing blocks have been moved into the new if … block. A set actor value Collected for … of group to true block has been inserted above the grow … block. A trigger event FruitCollected in behavior Score Management for this scene block has been inserted above the do after 0.5 seconds block. An if … of group is alive block has been inserted into the do after 0.5 seconds block, and the existing kill … of group block has been moved inside the newly added if … block. Test the game; collect several pieces of fruit, but not all of them! Examine the contents of the debug console; it may be necessary to scroll the console horizontally to read the messages. Continue to test the game, but this time collect all the fruit actors. What just happened? We have created a new Fruit Collected event in the Score Management scene behavior, which switches to a new scene when all the fruit actors have been collected, and we have also modified the Collides with Collectibles event in the Manage Player Collisions actor behavior in order to count how many pieces of fruit remain to be collected. When testing the game we can see that, each time a piece of fruit is collected, the new value of the Fruit Required attribute is displayed in the debug console, and when all the fruit actors have been collected, the yellow Level Completed scene is displayed. The first step was to create a blank Level Completed scene, which will be switched to when all the fruit actors have been collected. As with the Game Over scene that we created earlier in this article, it is a temporary scene that enables us to easily determine when the task of collecting the fruit has been completed successfully for testing purposes. We then created a new custom event called Fruit Collected in the Score Management scene behavior. This custom event waits for the FruitCollected event trigger to occur, and when that trigger is received, the Fruit Required attribute is decremented by 1 and its new value is displayed in the debug console. A test is then carried out to determine if the value of the Fruit Required attribute is equal to zero, and if it is equal to zero, the bright yellow, temporary Level Completed scene will be displayed! Our final task was to modify the Collides with Collectibles event in the Manage Player Collisions actor behavior. We inserted an if… block to test the collectible actor's Collected value; remember that we initialized this value to false in the previous section, Time for action – counting the fruit. If the Collected value for the fruit actor is still false, then it hasn't been collected yet, and the instructions contained within the if … block will be carried out. Firstly, the fruit actor's Collected value is set to false, which ensures that this event cannot occur again for the same piece of fruit. Next, the FruitCollected custom event in the Score Management scene behavior is triggered. Following that, the do after 0.5 seconds block is executed, and the fruit actor will be killed. We have also added an if … of group is alive check that is carried out before the collectible actor is killed. Because we are killing the actor after a delay of 0.5 seconds, it's good practice to ensure that the actor still exists before we try to kill it! In some games, it may be possible for the actor to be killed by other means during that very short 0.5 second delay, and if we try to kill an actor that does not exist, a runtime error may occur, that is, an error that happens while the game is being played. This may result in a technical error message being displayed to the player, and the game cannot continue; this is extremely frustrating for players, and they are unlikely to try to play our game again! Preventing multiple collisions from being detected A very common problem experienced by game designers, who are new to Stencyl, occurs when a collision between two actors is repeatedly detected. When two actors collide, all collision events that have been created with the purpose of responding to that collision will be triggered repeatedly until the collision stops occurring, that is, when the two actors are no longer touching. If, for example, we need to update the value of an attribute when a collision occurs, the attribute might be updated dozens or even hundreds of times in only a few seconds! In our game, we want collisions between the monkey actor and any single fruit actor to cause only a single update to the Fruit Required attribute. This is why we created the actor value Collected for each fruit actor, and this value is initialized to be false, not collected, by the Initialize Fruit Required event in the Score Management scene behavior. When the Collides with Collectibles event in Manage Player Collisions actor behavior is triggered, a test is carried out to determine if the fruit actor has already been collected, and if it has been collected, no further instructions are carried out. If we did not have this test, then the FruitCollected custom event would be triggered numerous times, and therefore the Fruit Required attribute would be decremented numerous times, causing the value of the Fruit Required attribute to reach zero almost instantly; all because the monkey collided with a single fruit actor! Using a Boolean value of True or False to carry out a test in this manner is often referred to by developers as using a flag or Boolean flag. Note that, rather than utilizing an actor value to record whether or not a fruit actor has been collected, we could have created a new attribute and initialized and updated the attribute in the same way that we initialized and updated the actor value. However, this would have required more effort to configure, and there is no perceptible impact on performance when using actor values in this manner. Some Stencyl users never use actor values (preferring to always use attributes instead), however, this is purely a matter of preference and it is at the discretion of the game designer which method to use. In order to demonstrate what happens when the actor value Collected is not used to determine whether or not a fruit actor has been collected, we can simply deactivate the set actor value Collected for … of group to true instruction block in the Collides with Collectibles event. After deactivating the block, run the game with the debug console open, and allow the monkey to collide with a single fruit actor. The Fruit Required attribute will instantly be decremented multiple times, causing the level to be completed after colliding with only one fruit actor! Remember to reactivate the set actor value … block before continuing! Keeping track of the levels As discussed in the introduction to this article, we're going to be adding an additional level to our game, so we'll need a method for keeping track of a player's progress through the game's levels. Time for action – adding a game attribute to record the level Create a new number game attribute called Level, with the configuration shown in the following screenshot: Save the game. What just happened? We have created a new game attribute called Level, which will be used to record the current level of the game. A game attribute is being used here, because we need to access this value in other scenes within our game; local attributes have their values reset whenever a scene is loaded, whereas game attributes' values are retained regardless of whether or not a different scene has been loaded. Fixing the never-ending game! We've finished designing and creating the gameplay for the Monkey Run game, and the scoring behaviors are almost complete. However, there is an anomaly with the management of the Lives game attribute. The monkey correctly loses a life when it collides with an enemy actor, but currently, when the countdown expires, the monkey is simply repositioned at the start of the level, and the countdown starts again from the beginning! If we leave the game as it is, the player will have an unlimited number of attempts to complete the level — that's not much of a challenge! Have a go hero (Recommended) In the Countdown expired event, which is found in the Health actor behavior, modify the test for the countdown so that it checks for the countdown timer being exactly equal to zero, rather than the current test, which is for the Countdown attribute being less than 1. We only want the ShowAngel event to be triggered once when the countdown equals exactly zero! (Recommended) Update the game so that the Show Angel event manages the complete process of losing a life, that is, either when a collision occurs between the monkey and an enemy, or when the countdown timer expires. A single event should deduct a life and restart the level. (Optional) If we look carefully, we can see that the countdown timer bar starts to grow backwards when the player runs out of time! Update the Display HUD event in the Score Management scene behavior, so that the timer bar is only drawn when the countdown is greater than zero. There are many different ways to implement the above modifications, so take some time and plan the recommended modifications! Test the game thoroughly to ensure that the lives are reduced correctly and the level restarts as expected, when the monkey collides with the enemy, and when the countdown expires. It would certainly be a good idea to review the download file for this session, compare it with your own solutions, and review each event carefully, along with the accompanying comment blocks. There are some useful tips in the example file, so do take the time to have a look! Summary Although our game doesn't look vastly different from when we started this article, we have made some very important changes. First, we implemented a text display to show the countdown timer, so that players of our game can see how much time they have remaining to complete the level. We also imported and configured a font and used the new font to make the countdown display more visually appealing. We then implemented a system of tracking the number of lives that the player has left, and this was our first introduction to learning how game attributes can store information that can be carried across scenes. The most visible change that we implemented in this article was to introduce a timer bar that reduces in size as the countdown decreases. Although very few instruction blocks were required to create the timer bar, the results are very effective, and are less distracting for the player than having to repeatedly look to the top of the screen to read a text display. The main challenge for players of our game is to collect all the fruit actors in the allocated time, so we created an initialization event to count the number of fruit actors in the scene. Again, this event has been designed to be reusable, as it will always correctly count the fruit actors in any scene. We also implemented the instructions to test when there are no more fruit actors to be collected, so the player can be taken to the next level in the game when they have completed the challenge. A very important skill that we learned while implementing these instructions was to use actor values as Boolean flags to ensure that collisions are counted only once. Finally, we created a new game attribute to keep track of our players' progress through the different levels in the game Resources for Article : Further resources on this subject: Introduction to Game Development Using Unity 3D [Article] 2D game development with Monkey [Article] Getting Started with GameSalad [Article]
Read more
  • 0
  • 0
  • 9802

article-image-bringing-your-game-life-ai-and-animations
Packt
26 Aug 2013
16 min read
Save for later

Bringing Your Game to Life with AI and Animations

Packt
26 Aug 2013
16 min read
(For more resources related to this topic, see here.) After going through these principles, we will be completing the tasks to enhance the maze game and the gameplay. We will apply animations to characters and trigger these in particular situations. We will improve the gameplay by allowing NPCs to follow the player where he/she is nearby (behavior based on distance), and attack the user when he/she is within reach. All material required to complete this article is available for free download on the companion website: http://patrickfelicia.wordpress.com/publications/books/unity-outbreak/. The pack for this article includes some great models and animations that were provided by the company Mixamo to enhance the quality of our final game. The characters were animated using Mixamo's easy online sequences and animation building tools. For more information on Mixamo and its easy-to-use 3D character rigging and animation tools, you can visit http://www.mixamo.com. Before we start creating our level, we will need to rename our scene and download the necessary assets from the companion website as follows: Duplicate the scene we have by saving the current scene (File Save | Scene), and then saving this scene as chapter5 (File | Save Scene As…). Open the link for the companion website: http://patrickfelicia.wordpress.com/publications/books/unity-outbreak/. Click on the link for the chapter5 pack to download this file. In Unity3D, create a new folder, chapter5, inside the Assets folder and select this folder (that is, chapter5). From Unity, select Assets | Import Package | Custom Package, and import the package you have just downloaded. This should create a folder, chapter5_pack, within the folder labeled chapter5. Importing and configuring the 3D character We will start by inserting and configuring the zombie character in the scene as shown in the following steps: Open the Unity Assets Store window (Window | Asset Store). In the Search field located in the top-right corner, type the text zombie. Click on the search result labeled Zombie Character Pack, and then click on the button labeled Import. In the new window entitled Importing package, uncheck the last box for the low-resolution zombie character and then click on Import. This will import the high-resolution zombie character inside our project and create a corresponding folder labeled ZombieCharacterPack inside the Assets folder. Locate the prefab zombie_hires by navigating to Assets | ZombieCharacterPack. Select this prefab and open the Inspector window, if it is not open yet. Click on the Rig tag, set the animation type to humanoid, and leave the other options as default. Click on the Apply button and then click on the Configure button; a pop-up window will appear: click on Save. In the new window, select: Mapping | Automap, as shown in the following screenshot: After this step, if we check the Hierarchy window, we should see a hierarchy of bones for this character. Select Pose | Enforce T-Pose as shown in the following screenshot: Click on the Muscles tab and then click on Apply in the new pop-up window. The Muscles tab makes it possible to apply constraints on our character. Check whether the mapping is correct by moving some of the sliders and ensuring that the character is represented properly. After this check, click on Done to go back to the previous window. Animating the character for the game Once we have applied these settings to the character, we will now use it for our scene. Drag-and-drop the prefab labeled zombie_hires by navigating to Assets | ZombieCharacterPack to the scene, change its position to (x=0, y =0, z=0), and add a collider to the character. Select: Component | Physics | Capsule Collider. Set the center position of this collider to (x=0, y=0.7, z=0), the radius to 0.5, the height to 2, and leave the other options as default, as illustrated in the following screenshot: Select: Assets | chapter5 | chapter5_pack; you will see that it includes several animations, including Zombie@idle, Zombie@walkForward, Zombie@attack, Zombie@hit, and Zombie@dead. We will now create the necessary animation for our character. Click once on the object zombie_hires in the Hierarchy window. We should see that it includes a component called Animator. This component is related to the animation of the character through Mecanim. You will also notice an empty slot for an Animator Controller. This controller will be created so that we can animate the character and control its different states, using a state machine. Let's create an Animator Controller that will be used for this character: From the project folder, select the chapter5 folder, then select Create | Animator Controller in the Project window. This should create a new Animator Controller labeled New Animator Controller in the folder chapter5. Rename this controller zombieController. Select the object labeled zombie_hires in the Hierarchy window. Locate the Animator Controller that we have just created by navigating to Assets | chapter5 (zombieController), drag-and-drop it to the empty slot to the right of the attribute controller in the Animator component of the zombie character, and check that the options Apply Root Motion and Animate Physics are selected. Our character is now ready to receive the animations. Open the Animator window (Window | Animator). This window is employed to display and manage the different states of our character. Since no animation is linked to the character, the default state is Any State. Select the object labeled zombie_hires in the Hierarchy window. Rearrange the windows in our project so that we can see both the state machine window and the character in the Scene view: we can drag the tab labeled Scene for the Scene view at the bottom of the Animator window, so that both windows can be seen simultaneously. We will now apply our first animation to the character: Locate the prefab Zombie@idle by navigating to Assets | chapter5 | chapter5_pack. Click once on this prefab, and in the Inspector window, click the Rig tab. In the new window, select the option Humanoid for the attribute Animation Type and click on Apply. Click on the Animations tab, and then click on the label idle, this will provide information on the idle clip. Scroll down the window, check the box for the attribute Loop Pose, and click on Apply to apply this change (you will need to scroll down to locate this button). In the Project view, click on the arrow located to the left (or right, depending on how much we have zoomed-in within this window) of the prefab Zombie@idle; it will reveal items included in this prefab, including an animation called idle, symbolized by a gray box with a white triangle. Make sure that the Animator window is active and drag this animation (idle) to the Animator window. This will create an idle state, and this state will be colored in orange, which means that it is the default state for our character. Rename this state Idle (upper case I) using the Inspector. Play the scene and check that the character is in an idle state. Repeat steps 1-9 for the prefab Zombie@walkForward and create a state called WalkForward. To test the second animation, we can temporarily set the state walkForward to be the default state by right-clicking on the walkForward state in the Animator window, and selecting Set As Default. Once we have tested this animation, set the state Idle as the default state. While the zombie is animated properly, you may notice that the camera on the First Person Controller might be too high. You will address this by changing the height of the camera so that it is at eye-level. In the Hierarchy view, select the object Main Camera that is located with the object First Person Controller and change its position to (x=0, y=0.5, z=0). We now have two animations. At present, the character is in the Idle state, and we need to de fine triggers or conditions for the character to start or stop walking toward the player. In this game, we will have enemies with different degrees of intelligence. This first type will follow the user when it sees the user, is close to the user, or is being attacked by the user. The Animator window will help to create animations and to apply transition conditions and blending between them so that transitions between each animation are smoother. To move around this window, we can hold the Alt key while simultaneously dragging-and-dropping the mouse. We can also select states by clicking on them or de fining a selection area (drag-and-drop the mouse to define the area). If needed, it is also possible to maximize this window using the icon located at its top-right corner. Creating parameters and transitions First, let's create transitions. Open the Animator window, right-click on the state labeled Idle, and select the option Make Transition from the contextual menu. This will create an arrow that symbolizes the transition from this state to another state. While this arrow is visible, click on the state labeled WalkForward. This will create a transition between the states WalkForward and Idle as illustrated in the following screenshot: Repeat the last step to create a transition between the state WalkForward and Idle: right-click on the state labeled WalkForward, select the option Make Transition from the contextual menu, and click on the state labeled Idle. Now that these transitions have been defined, we will need to specify how the animations will change from one state to the other. This will be achieved using parameters. In the Animator window, click on the + button located at the bottom-right corner of the window, as indicated in the following screenshot: Doing so will display a contextual menu, from which we can choose the type of the parameter. Select the option Bool to create a Boolean parameter. A new window should now appear with a default name for our new parameter as illustrated in the following screenshot: change the name of the parameter to walking. Now that the parameter has been defined, we can start defining transitions based on this parameter. Let's start with the first transition from the Idle state to the Walkforward state: Select the transition from the Idle state to the Walkforward state (that is, click on the corresponding transition in the Animator window). If we look at the Inspector window, we can see that this object has several components, including Transitions and Conditions. Let's focus on the Conditions component for the time being. We can see that the condition for the transition is based on a parameter called ExitTime and that the value is 0.98. This means that the transition will occur when the current animation has reached 98 percent completion. However, we would like to use the parameter labeled walking instead. Click on the parameter ExitTime, this should display other parameters that we can use for this transition. Select walking from the contextual menu and make sure that the condition is set to true as shown in the following screenshot: The process will be similar for the other transition (that is, from WalkForward to Idle), except that the condition for the transition for the parameter walking will be false: select the second transition (WalkForward to Idle) and set the transition condition of walking to false. To check that the transitions are working, we can do the following: Play the scene and look at the Scene view (not the Game view). In the Animator window, change the parameter walking to true by checking the corresponding box, as highlighted in the following screenshot: Check that the zombie character starts walking; click on this box again to set the variable walking to false, check that the zombie stops walking, and stop the Play mode (Ctrl + P). Adding basic AI to enemies We have managed to set transitions for the animations and the state of the zombie from Idle to walking. To add some challenge to the game, we will equip this enemy with some AI and create a script that changes the state of the enemy from Idle to WalkForward whenever it sees the player. First, let's allocate the predefined-tag player to First Person Controller: select First Person Controller from the Hierarchy window, and in the Inspector window, click on the drop-down menu to the right of the label Tag and select the tag Player. Then, we can start creating a script that will set the direction of the zombie toward the player. Create a folder labeled Scripts inside the folder Assets | chapter5, create a new script, rename it controlZombie, and add the following code to the start of the script: public var walking:boolean = false;public var anim:Animator;public var currentBaseState:AnimatorStateInfo; public var walkForwardState:int = Animator.StringToHash("Base Layer.WalkForward");public var idleState:int = Animator.StringToHash("Base Layer.Idle");private var playerTransform:Transform;private var hit:RaycastHit; In statement 1 of the previous code, a Boolean value is created. It is linked to the parameter used for the animation in the Animator window. In statement 2 of the previous code, we define an Animator object that will be used to manage the animator component of the zombie character. In statement 3 of the previous code, we create an AnimatorStateInfo variable that will be used to determine the current state of the animation (for example, Idle or WalkForward). In statement 4 of the previous code, we create a variable, walkForwardState , that will represent the state WalkForward previously de fined in the Animator window. We use the method Animator.StringToHash to convert this state initially from a string to an integer that can then be used to monitor the active state. In statement 5 of the previous code, similar to the previous comments, a variable is created for the state Idle. In statement 6 of the previous code, we create a variable that will be used to detect the position of the player. In statement 7 of the previous code, we create a ray that will be employed later on to detect the player. Next, let's add the following function to the script: function Start (){ anim = GetComponent(Animator); playerTransform = GameObject.FindWithTag("Player").transform;} In line 3 of the previous code, we initialize the variable anim with the Animator component linked to this GameObject. We can then add the following lines of code: function Update (){ currentBaseState = anim.GetCurrentAnimatorStateInfo(0); gameObject.transform.LookAt(playerTransform);} In line 3 of the previous code, we determine the current state for our animation. In line 4 of the previous code, the transform component of the current game object is oriented so that it is looking at the First Person Controller. Therefore, when the zombie is walking, it will follow the player. Save this script, and drag-and-drop it to the character labeled zombie_hires in the Hierarchy window. As we have seen previously, we will need to manage several states through our script, including the states Idle and WalkForward. Let's add the following code in the Update function: switch (currentBaseState.nameHash){case idleState:break;case walkForwardState:break;default:break;} In line 1 of the previous code, depending on the current state, we will switch to a different set of instructions All code related to the state Idle will be included within lines 3-4 of the previous code All code related to the state WalkForward will be included within lines 6-7 If we play the scene, we may notice that the zombie rotates around the x and z axes when near the player; its y position also changes over time. To correct this issue, let's add the following code at the end of the function Update: transform.position.y = -0.5;transform.rotation.x = 0.0;transform.rotation.z = 0.0; We now need to detect whether the zombie can see the player, or detect its presence within a radius of two meters(that is, the zombie would hear the player if he/she is within two meters). This can be achieved using two techniques: by calculating the distance between the zombie and the player, and by casting a ray from the zombie and detecting whether the player is in front of the zombie. If this is the case, the zombie will start walking toward the player. We need to calculate the distance between the player and the zombie by adding the following code to the script controlZombie, at the start of the function Update, before the switch statement: var distance:float = Vector3.Distance(transform.position, playerTransform.position); In the previous code, we create a variable labeled distance and initialize it with the distance between the player and the zombie. This is achieved using the built-in function Vector3.Distance. Now that the distance is calculated (and updated in every frame), we can implement the code that will serve to detect whether the player is near or in front of the zombie. Open the script entitled controlZombie, and add the following lines to the function Update within the block of instructions for the Idle state, so that it looks as follows: case idleState: if ((Physics.Raycast (Vector3(transform.position.x,transform.position.y+.5,transform.po sition.z), transform.forward, hit,40) && hit.collider.gameObject.tag == "Player") || distance <2.0f) { anim.SetBool("walking",true); }break; In the previous lines of code, a ray or ray cast is created. It is casted forward from the zombie, 0.5 meters above the ground and over 40 meters. Thanks to the variable hit, we read the tag of the object that is colliding with our ray and check whether this object is the player. If this is the case, the parameter walking is set to true. Effectively, this should trigger a transition to the state walking, as we have defined previously, so that the zombie starts walking toward the player. Initially, our code was written so that the zombie rotated around to face the player, even in the Idle state (using the built-in function LookAt). However, we need to modify this feature so that the zombie only turns around to face the player while it is following the player, otherwise, the player will always be in sight and the zombie will always see him/her, even in the Idle state. We can achieve this by deleting the code highlighted in the following code snippet (from the start of the function Update), and adding it to the code for the state WalkForward: case walkForwardState: transform.LookAt(playerTransform); break; In the previous lines, we checked whether the zombie is walking forward, and if this is the case, the zombie will rotate in order to look at and follow the player. Test our code by playing the scene and either moving within two meters of the zombie or in front of the zombie.
Read more
  • 0
  • 0
  • 9613
article-image-modeling-furniture-blender
Packt
22 Oct 2009
10 min read
Save for later

Modeling Furniture in Blender

Packt
22 Oct 2009
10 min read
Create Models or Use a Library? There are two possibilities when working with furniture. We can create new furniture, or use pre-made models from a library. The question is: when must we use each type? Some people say that using a pre-made model is not very professional thing but what they forget to say is that most projects have a tight deadline, and we need a quick modeling process to be ready on time. So, what's most important for professionals? Getting things done, or telling the client that all the models were created just for his project? Of course, the deadline is the most important, and your clients normally won't mind if you use pre-made models. Probably they won't even notice. So don't be ashamed to use pre-made models they won't make your projects any less professional. It's even recommended to use these models to speed-up the process, and allow you to spend more time on lighting or texturing. Is there any situation that demands the creation of a furniture model from scratch? Well, there are some. First, if you can't find the model in any library that you know, then it's going to be necessary to create it from scratch. If you are working with an architect who designs the spaces and furniture as well, you will probably have to model the furniture too, since it won't be available at any public library. Any project that deals with customized furniture will require that we work on the modeling for the furniture. Create your own libraryA good practice for anyone doing architectural visualization is to collect a lot of 3D models from public libraries for use in future projects. Keep these models for later, but don't forget to check if the author has released the models with no restrictions for commercial use. Otherwise, you must get their permission to use them. If you want to create your library, with no restrictions, why not create your own models? This could be a good exercise: take a few examples, and start creating some furniture. With time, you will have a good number of models. How to Get Started? In most cases, we have to get used to all that furniture modeling. We will have to start from scratch, with no blueprints available. The only references that we will have would be the photos, either provided by our clients, or provided from some web resources. If you have the time, visit a real store, and take some pictures and measures on your own. Sometimes, these stores will give you fliers and brochures, especially if you work with architecture. With time, you will get a lot of good reference material, and some of them come with measurements. But, if you don't know where to get started, let me point out some great web resources: http://www.e-interiors.net http://resources.blogoscopia.com http://blender-archi.tuxfamily.org/Models http://www.katorlegaz.com/ http://sketchup.google.com/3dwarehouse The first link has a lot of reference images classified by furniture type and designer. And sometimes, they even provide free 3D models. Most models there are saved in DXF, or 3DS file formats. Appending Models Before we start to model, let's see how we can import a model form an external library into Blender. The process is very simple, and what we have to do is to use the File menu, and access the Append or Link option. There is a shortcut for that too - just press SHIFT+F1 to call the same function. With this option, we have to select file that is already in the Blender file format. This option won't import files in other formats. When we select a file, a list of elements available in that particular file will be displayed, for us to select what we want to import. In most cases, the models will be stored under Object. When we click the Object option, all of the objects available in the file will be listed. If you know the name of the object that you want to import, just select the name, and click Load Library. The object will be loaded into our scene. Here, we have two options to handle this object: Append or Link: Append: If we choose this option, the object will be merged into ourcurrent scene. Link: With this option, an external link to the object file will be created. Any modifications to the original file will be reflected in our current scene. What is the best method to use? It will depend on whether we are willing to track all modifications applied to our furniture models. Using the Link method is a great way of keeping the furniture updated, because every modification at the original file is reflected immediately in the scene in which this model is placed. However, we will have to take the original file with the scene file every time we need to put our scene on another computer. They always have to go together. But if you choose to use the Append option, things will be a bit simpler, because the object will be incorporated into the scene file. We won't have to be worried about moving the furniture file along with the scene. Always use the Append option when you want to use furniture, or any other model, saved in another Blender file. To use a furniture model saved in another file, with a type other than “.blend”, we have to use the Import option. Importing Models To import a model, the process is very simple. We must use the File menu and select, Import. Then we have to select the proper file type from the list. The best file type, and the most common for furniture blocks, is the 3Ds file format, which belongs to the old 3D Studio application. There are some other good formats that work well with Blender, such as OBJ and LWO. The 3Ds file format can store lights, and it works well with Blender. The only thing we have to take extra care about is that most models imported come with triangular faces, which are a bit harder to edit. But, if you don't need to make any modifications to the model, this won't be a problem. Append or Import?Just to make things clearer, if you download a furniture model from a web site, and it's saved in the Blender native file format (.blend), you should append the model. If you download or get a furniture model on any file format other than “.blend”, you will have to import it. Since most models aren't saved in the Blender native file format, we can safely say that almost all furniture models that you find will require an import action to be placed in your scenes. Modeling a Chair Let's start with something simple, such as a chair. Even for a simple model, it will help us deal with smaller dimensions and details. Here is an image of the model: What's the main objective of this modeling? We have to create this chair, with the minimum use of faces and vertices. A good amount of detail can be left for textures, and it's always a good choice to use a lower number of vertices and faces in a model. If you consider one model, it won't matter much. But with a large number of chairs, such as in a theater room, it can make a difference in render time. Let's get started with a simple cube. Select this cube, and change the work mode to Edit. Select all vertices and press the W key. This will open the Specials menu. Choose subdivide, just once, from this menu. This will create new vertices and edges. Once these new vertices have been created, as shown in the image to the left, below, press the A key to remove all of the objects from the selection. Now, select the vertices to the right, using the B key. Remember to change the view mode to Wireframe before using the B key, otherwise, we won't be able to select the vertices behind the visible faces. When these vertices are selected, press the X key and choose Vertices to erase only the selected vertices. Using the CTRL+R key, add a new edge loop to the model, as shown in the following image: The next step is to change the scale of our model. Rotate the view to see the model in perspective view. Select all objects and press the S key, immediately after pressing the Z key. This will make the scale work only in the Z axis. Now, select the vertices identified in the following image and erase them using the X key. Change the selection mode to Edges, and select the edges identified in the following image. With the edges selected, press the E key to extrude them. With the new faces created, we can now add some detail to the model. Select only the top edge of the previously created faces. Move this edge down just a bit. This will add a small declivity to the seat. Now, we can move on to the next extrude, which must be from the selected edges identified in the following image. I'm not using any kind of measure for this example, but if you like to work only with real measurements, remember to hold the CTRL key every time a new extrude or edge is moved. This way, all transformations will use the grid lines. For this model, I'm not using vertex snap. With the new faces created, select just the two edges identified in the following image. Extrude these edges until they reach the other side of the base model. Hold the CTRL key, while you extrude them, to help with the precision. If you already want to remove duplicated vertices, select all objects, and press the W key. Choose Remove doubles to erase any duplicated vertices. Select the edges identified in the image to keep adding more parts to the chair. Extrude the edges three times until you have the same structure showed here. Now, we have to close the top with a face. To do that, we must select all four vertices on the top. When the vertices are selected, press the F key to create a new face. The next step is to select the small side edges to create some detail. Select just one edge, beginning from bottom to top, and move it just a bit. Repeat this operation with the other edges until we get the edges positioned as in the following image. The basic shape of our chair has now been created. Now, we can make some adjustments for improving the overall proportions. Select all edges or vertices on the left side, and move them a bit to the left. This will make the model wider. Did you notice that we have modeled only half a chair? Now we can make the other half, using the Mirror modifier. Add the modifier, and choose the right axis to make a perfect copy. If the center point for the model has been moved, you might need to edit the model to create a perfect mirrored match. Don't worry if you have moved the model by accident - this can happen sometimes. Along with the Mirror modifier, add a Subsurf modifier, too. With the Subsurf modifier, we realize that this model needs a new edge loop on the left side. Just press CTRL+R, and add a new loop, as in the following image.
Read more
  • 0
  • 0
  • 9612

article-image-panda3d-game-development-scene-effects-and-shaders
Packt
20 Apr 2011
8 min read
Save for later

Panda3D game development: scene effects and shaders

Packt
20 Apr 2011
8 min read
While brilliant gameplay is the key to a fun and successful game, it is essential to deliver beautiful visuals to provide a pleasing experience and immerse the player in the game world. The looks of many modern productions are massively dominated by all sorts of visual magic to create the jaw-dropping visual density that is soaked up by players with joy and makes them feel connected to the action and the gameplay they are experiencing. The appearance of your game matters a lot to its reception by players. Therefore it is important to know how to leverage your technology to get the best possible looks out of it. This is why this article will show you how Panda3D allows you to create great looking games using lights, shaders, and particles. Adding lights and shadows in Panda3d Lights and shadows are very important techniques for producing a great presentation. Proper scene lighting sets the mood and also adds depth to an otherwise flat-looking scene, while shadows add more realism, and more importantly, root the shadow-casting objects to the ground, destroying the impression of models floating in mid-air. This recipe will show you how to add lights to your game scenes and make objects cast shadows to boost your visuals. Getting ready You need to create the setup presented in Setting up the game structure before proceeding, as this recipe continues and builds upon this base code. How to do it... This recipe consists of these tasks: Add the following code to Application.py: from direct.showbase.ShowBase import ShowBase from direct.actor.Actor import Actor from panda3d.core import * class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.panda = Actor("panda", {"walk": "panda-walk"}) self.panda.reparentTo(render) self.panda.loop("walk") cm = CardMaker("plane") cm.setFrame(-10, 10, -10, 10) plane = render.attachNewNode(cm.generate()) plane.setP(270) self.cam.setPos(0, -40, 6) ambLight = AmbientLight("ambient") ambLight.setColor(Vec4(0.2, 0.1, 0.1, 1.0)) ambNode = render.attachNewNode(ambLight) render.setLight(ambNode) dirLight = DirectionalLight("directional") dirLight.setColor(Vec4(0.1, 0.4, 0.1, 1.0)) dirNode = render.attachNewNode(dirLight) dirNode.setHpr(60, 0, 90) render.setLight(dirNode) pntLight = PointLight("point") pntLight.setColor(Vec4(0.8, 0.8, 0.8, 1.0)) pntNode = render.attachNewNode(pntLight) pntNode.setPos(0, 0, 15) self.panda.setLight(pntNode) sptLight = Spotlight("spot") sptLens = PerspectiveLens() sptLight.setLens(sptLens) sptLight.setColor(Vec4(1.0, 0.0, 0.0, 1.0)) sptLight.setShadowCaster(True) sptNode = render.attachNewNode(sptLight) sptNode.setPos(-10, -10, 20) sptNode.lookAt(self.panda) render.setLight(sptNode) render.setShaderAuto() Start the program with the F6 key. You will see the following scene: How it works... As we can see when starting our program, the panda is lit by multiple lights, casting shadows onto itself and the ground plane. Let's see how we achieved this effect. After setting up the scene containing our panda and a ground plane, one of each possible light type is added to the scene. The general pattern we follow is to create new light instances before adding them to the scene using the attachNewNode() method. Finally, the light is turned on with setLight(), which causes the calling object and all of its children in the scene graph to receive light. We use this to make the point light only affect the panda but not the ground plane. Shadows are very simple to enable and disable by using the setShadowCaster() method, as we can see in the code that initializes the spotlight. The line render.setShaderAuto() enables the shader generator, which causes the lighting to be calculated pixel perfect. Additionally, for using shadows, the shader generator needs to be enabled. If this line is removed, lighting will look coarser and no shadows will be visible at all. Watch the amount of lights you are adding to your scene! Every light that contributes to the scene adds additional computation cost, which will hit you if you intend to use hundreds of lights in a scene! Always try to detect the nearest lights in the level to use for lighting and disable the rest to save performance. There's more... In the sample code, we add several types of lights with different properties, which may need some further explanation. Ambient light sets the base tone of a scene. It has no position or direction—the light color is just added to all surface colors in the scene, which avoids unlit parts of the scene to appear completely black. You shouldn't set the ambient color to very high intensities. This will decrease the effect of other lights and make the scene appear flat and washed out. Directional lights do not have a position, as only their orientation counts. This light type is generally used to simulate sunlight—it comes from a general direction and affects all light-receiving objects equally. A point light illuminates the scene from a point of origin from which light spreads towards all directions. You can think of it as a (very abstract) light bulb. Spotlights, just like the headlights of a car or a flashlight, create a cone of light that originates from a given position and points towards a direction. The way the light spreads is determined by a lens, just like the viewing frustum of a camera. Using light ramps The lighting system of Panda3D allows you to pull off some additional tricks to create some dramatic effects with scene lights. In this recipe, you will learn how to use light ramps to modify the lights affect on the models and actors in your game scenes. Getting ready In this recipe we will extend the code created in Adding lights and shadows found in this article. Please review this recipe before proceeding if you haven't done so yet. How to do it... Light ramps can be used like this: Open Application.py and add and modify the existing code as shown: from direct.showbase.ShowBase import ShowBase from direct.actor.Actor import Actor from panda3d.core import * from direct.interval.IntervalGlobal import * class Application(ShowBase): def __init__(self): ShowBase.__init__(self) self.panda = Actor("panda", {"walk": "panda-walk"}) self.panda.reparentTo(render) self.panda.loop("walk") cm = CardMaker("plane") cm.setFrame(-10, 10, -10, 10) plane = render.attachNewNode(cm.generate()) plane.setP(270) self.cam.setPos(0, -40, 6) ambLight = AmbientLight("ambient") ambLight.setColor(Vec4(0.3, 0.2, 0.2, 1.0)) ambNode = render.attachNewNode(ambLight) render.setLight(ambNode) dirLight = DirectionalLight("directional") dirLight.setColor(Vec4(0.3, 0.9, 0.3, 1.0)) dirNode = render.attachNewNode(dirLight) dirNode.setHpr(60, 0, 90) render.setLight(dirNode) pntLight = PointLight("point") pntLight.setColor(Vec4(3.9, 3.9, 3.8, 1.0)) pntNode = render.attachNewNode(pntLight) pntNode.setPos(0, 0, 15) self.panda.setLight(pntNode) sptLight = Spotlight("spot") sptLens = PerspectiveLens() sptLight.setLens(sptLens) sptLight.setColor(Vec4(1.0, 0.4, 0.4, 1.0)) sptLight.setShadowCaster(True) sptNode = render.attachNewNode(sptLight) sptNode.setPos(-10, -10, 20) sptNode.lookAt(self.panda) render.setLight(sptNode) render.setShaderAuto() self.activeRamp = 0 toggle = Func(self.toggleRamp) switcher = Sequence(toggle, Wait(3)) switcher.loop() def toggleRamp(self): if self.activeRamp == 0: render.setAttrib(LightRampAttrib.makeDefault()) elif self.activeRamp == 1: render.setAttrib(LightRampAttrib.makeHdr0()) elif self.activeRamp == 2: render.setAttrib(LightRampAttrib.makeHdr1()) elif self.activeRamp == 3: render.setAttrib(LightRampAttrib.makeHdr2()) elif self.activeRamp == 4: render.setAttrib(LightRampAttrib. makeSingleThreshold(0.1, 0.3)) elif self.activeRamp == 5: render.setAttrib(LightRampAttrib. makeDoubleThreshold(0, 0.1, 0.3, 0.8)) self.activeRamp += 1 if self.activeRamp > 5: self.activeRamp = 0 Press F6 to start the sample and see it switch through the available light ramps as shown in this screenshot: How it works... The original lighting equation that is used by Panda3D to calculate the final screen color of a lit pixel limits color intensities to values within a range from zero to one. By using light ramps we are able to go beyond these limits or even define our own ones to create dramatic effects just like the ones we can see in the sample program. In the sample code, we increase the lighting intensity and add a method that switches between the available light ramps, beginning with LightRampAttrib.makeDefault() which sets the default clamping thresholds for the lighting calculations. Then, the high dynamic range ramps are enabled one after another. These light ramps allow you to have a higher range of color intensities that go beyond the standard range between zero and one. These high intensities are then mapped back into the displayable range, allocating different amounts of values within it to displaying brightness. By using makeHdr0(), we allocate a quarter of the displayable range to brightness values that are greater than one. With makeHdr1() it is a third and with makeHdr2() we are causing Panda3D to use half of the color range for overly bright values. This doesn't come without any side effects, though. By increasing the range used for high intensities, we are decreasing the range of color intensities available for displaying colors that are within the limits of 0 and 1, thus losing contrast and making the scene look grey and washed out. Finally, with the makeSingleThreshold() and makeDoubleThreshold() methods, we are able to create very interesting lighting effects. With a single threshold, lighting values below the given limit will be ignored, while anything that exceeds the threshold will be set to the intensity given in the second parameter of the method. The double threshold system works analogous to the single threshold, but lighting intensity will be normalized to two possible values, depending on which of the two thresholds was exceeded.
Read more
  • 0
  • 0
  • 9439
Modal Close icon
Modal Close icon