Objectives
We introduce basic entities and physic concepts in Wave Engine to make all them work together by creating a world with a ground and a wall with physic interaction.
Create Project
So create a new Game Project from the Wave Engine template, and name it PhysicWall:
Create the camera
First thing we are going to need is a camera to see the world from. So, go to MyScene.cs, remove the Sample Test region and add these usings :
csharp code:
using WaveEngine.Components.Cameras;
Create a camera adding this code at the end of the CreateScene() method.:
csharp code:
{
BackgroundColor = Color.CornflowerBlue
};
EntityManager.Add(camera);
We are creating a camera, adding it to the EntityManager, as the camera is an Entity, and set it as the active camera.
The FreeCamera default behavior allow us to move in our world by using the keys a,w,s,d and if we press the left mouse button we can look around.
Create the ground
We are going to create the ground for our world. It will consist in a cube, with a 3D transformation that set his height to 1. But first, we need to add these usings to MyScene.cs file:
csharp code:
using WaveEngine.Framework.Physics3D;
using WaveEngine.Components.Graphics3D;
using WaveEngine.Materials;
Now copy the next code to create the ground at the end of CreateScene() method:
csharp code:
.AddComponent(new Transform3D() { Position = new Vector3(0, -1, 0), Scale = new Vector3(100, 1, 100) })
.AddComponent(new BoxCollider())
.AddComponent(Model.CreateCube())
.AddComponent(new RigidBody3D() { IsKinematic = true })
.AddComponent(new MaterialsMap(new BasicMaterial(Color.White)))
.AddComponent(new ModelRenderer());
EntityManager.Add(ground);
The Transform3D() component indicates the ground is positioned in the world origin in X and Z axis, but -1 in Y axis. And the Scale set the size of the ground to 100 in width, 1 in height and 100 in length, this Scale is related to the Model.CreateCube() component that create a 1x1x1 cube by default.
The BoxCollider() sets the very basic collision algorithm that will apply to our ground, that related with the RigidBody3D() sets the default properties for the physic calculations, but modifies the default IsKinematic property to true.
The MaterialsMap component sets the texture or list of textures that the ground will have. By now, we only set a plane color Color.gray. Later we will apply a texture to it.
Now, if you build and run you will get this:
Now you can move in the world due to the FreeCamera default behavior by using w,a,s,d to move and the left mouse button to look around.
Create a brick
Now, we will repeat the same process to create a brick as we did with the ground, so to create a simple brick use this code at the end of CreateScene() method:
csharp code:
.AddComponent(new Transform3D() { Position = new Vector3( 0.5f,5f ,0), Scale = new Vector3(1f, 0.5f, 0.5f)})
.AddComponent(new MaterialsMap(new BasicMaterial(Color.Red)))
.AddComponent(Model.CreateCube())
.AddComponent(new BoxCollider())
.AddComponent(new RigidBody3D() { Mass = 100 })
.AddComponent(new ModelRenderer());
EntityManager.Add(brick);
When you build and run you will get a brick falling to the ground:
Use your keyboard and mouse to move the camera and see the 3D brick.
Create the wall
Now, we will create a wall with basic bricks, so encapsulate the brick creation code with this new function:
csharp code:
{
Entity primitive = new Entity(name)
.AddComponent(new Transform3D() { Position = position, Scale = scale })
.AddComponent(new MaterialsMap(new BasicMaterial(Color.Red)))
.AddComponent(Model.CreateCube())
.AddComponent(new BoxCollider())
.AddComponent(new RigidBody3D() { Mass = mass })
.AddComponent(new ModelRenderer());
return primitive;
}
Now we will replace the first block code with two for loops that will create a wall, so place this code where the simple box was:
csharp code:
int height = 10;
float blockWidth = 2f;
float blockHeight = 1f;
float blockLength = 1f;
int n = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
n++;
var toAdd = CreateBox("box" + n, new Vector3(i * blockWidth + .5f * blockWidth * (j % 2) - width * blockWidth * .5f,
blockHeight * .5f + j * (blockHeight),
0),
new Vector3(blockWidth, blockHeight, blockLength), 10);
EntityManager.Add(toAdd);
}
}
Just not to get lost, the MyScene.cs code look like this:
csharp code:
using System;
using WaveEngine.Common;
using WaveEngine.Common.Graphics;
using WaveEngine.Common.Math;
using WaveEngine.Components.Cameras;
using WaveEngine.Components.Graphics2D;
using WaveEngine.Components.Graphics3D;
using WaveEngine.Framework;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Resources;
using WaveEngine.Framework.Services;
using WaveEngine.Common.Math;
using WaveEngine.Components.Cameras;
using WaveEngine.Framework.Graphics;
using WaveEngine.Framework.Physics3D;
using WaveEngine.Components.Graphics3D;
using WaveEngine.Materials;
#endregion
namespace PhysicWallProject
{
public class MyScene : Scene
{
protected override void CreateScene()
{
//Insert your scene definition here.
FreeCamera camera = new FreeCamera("MainCamera", new Vector3(0, 10, 20), new Vector3(0, 5, 0))
{
BackgroundColor = Color.CornflowerBlue
};
EntityManager.Add(camera);
Entity ground = new Entity("Ground")
.AddComponent(new Transform3D() { Position = new Vector3(0, -1, 0), Scale = new Vector3(100, 1, 100) })
.AddComponent(new BoxCollider())
.AddComponent(Model.CreateCube())
.AddComponent(new RigidBody3D() { IsKinematic = true })
.AddComponent(new MaterialsMap(new BasicMaterial(Color.White)))
.AddComponent(new ModelRenderer());
EntityManager.Add(ground);
int width = 10;
int height = 10;
float blockWidth = 2f;
float blockHeight = 1f;
float blockLength = 1f;
int n = 0;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
n++;
var toAdd = CreateBox("box" + n, new Vector3(i * blockWidth + .5f * blockWidth * (j % 2) - width * blockWidth * .5f,
blockHeight * .5f + j * (blockHeight),
0),
new Vector3(blockWidth, blockHeight, blockLength), 10);
EntityManager.Add(toAdd);
}
}
}
protected override void Start()
{
base.Start();
// This method is called after the CreateScene and Initialize methods and before the first Update.
}
private Entity CreateBox(string name, Vector3 position, Vector3 scale, float mass)
{
Entity primitive = new Entity(name)
.AddComponent(new Transform3D() { Position = position, Scale = scale })
.AddComponent(new MaterialsMap(new BasicMaterial(Color.Red)))
.AddComponent(Model.CreateCube())
.AddComponent(new BoxCollider())
.AddComponent(new RigidBody3D() { Mass = mass })
.AddComponent(new ModelRenderer());
return primitive;
}
}
}
Now if we build and run we will get this:
But we are going to make it a little more fun, so create a method to return random colors like this:
csharp code:
{
var random = WaveServices.Random;
return new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble(), 1f);
}
And in the CreateBox() method change the line that sets the material for the box:
csharp code:
With this one:
csharp code:
Now if we build and run, a very funny wall will appear:
Create the ball
We are going to create a ball every time users click the left mouse button or tap the screen that will interact with the wall.
We need to add a Behavior. A Behavior is a component that has a logic update. As a component a behavior can be added to an Entity, and it provides an Update method to modify the Entity logic which is associated to, so create a new class to the PhysicWallProject project and name it as FireBehavior.
Add the usings:
csharp code:
using WaveEngine.Framework.Graphics;
So we can define a behavior, and make de FireBehavior class to inherit from Behavior:
csharp code:
Now add these class variables:
csharp code:
Entity sphere;
[RequiredComponent]
public Camera3D Camera;
The pressed variable will be useful to check if left mouse button has been pressed or screen has been tapped. Sphere entity is the ball that will be thrown. Camera is the component we will associate this behavior and with [RequiredComponent] we indicates that is a component requirement. So if we do not associate this behavior to a camera, an exception will be thrown.
This is the default constructor:
csharp code:
: base("FireBehavior")
{
Camera = null;
}
Default constructor just calls the base constructor with a string representing the behavior name, and initializes Camera variable.
We need to add these usings:
csharp code:
using WaveEngine.Components.Graphics3D;
using WaveEngine.Common.Math;
using WaveEngine.Materials;
using WaveEngine.Common.Graphics;
using WaveEngine.Framework.Physics3D;
We are going to add the logic to the Update method by overriding it in this way:
csharp code:
{
var touches = WaveServices.Input.TouchPanelState;
if (touches.Count > 0)
{
if (!pressed)
{
pressed = true;
if (sphere == null)
{
sphere = new Entity("ball")
.AddComponent(new Transform3D() { Scale = new Vector3(2) })
.AddComponent(new MaterialsMap(new BasicMaterial(Color.Gray)))
.AddComponent(Model.CreateSphere())
.AddComponent(new SphereCollider())
.AddComponent(new RigidBody3D() { Mass = 3, EnableContinuousContact = true })
.AddComponent(new ModelRenderer());
EntityManager.Add(sphere);
}
RigidBody3D rigidBody = sphere.FindComponent<RigidBody3D>();
rigidBody.ResetPosition(Camera.Position);
var direction = Camera.LookAt - Camera.Position;
rigidBody.ApplyLinearImpulse(500 * direction);
}
}
else
{
pressed = false;
}
}
With this method we are modifying the Update method from the Entity associated. First we need access to the TouchPanelState that represents the device touch panel state. WaveServices.Input gave access to all device input sensors as compass, accelerometer, etc. In this case we need access to touch panel state.
Next we control the touch and click gestures just not to initialize and throw the ball every time Update method is executed, so if the ball is not initialized we create one with red color, scale it to 2 in 3D, use a SphereCollider and set the RigidBody3D parameters to calculates collision.
Once the ball has been initialized and Update method detect it is time to throw a ball, it sets the position as the Camera position and apply an impulse with ApplyLinearImpulse from the rigidBody sphere component.
With this code, we have a behavior that makes every time we click or tap the screen, a ball will be thrown.
Last thing we have to do is to associate this behavior to our camera, so go to MyScene.cs file, locate the camera initialization and modify the initialization code to look like this
csharp code:
{
BackgroundColor = Color.CornflowerBlue
};
camera.Entity.AddComponent(new FireBehavior());
EntityManager.Add(camera);
You can build/run and interact with the wall:
Adding textures
It is time to apply some texture to our object. You can search the web, create yours or use the textures located in Textures tutorial directory.
We need to add a .png and a .fbx file located at the Resources tutorial folder into the Asset Exporter. Click on the Resources.weproj file placed into the PhysicWallProject project:
Click on the Add button:
Navigate to the files we want and click "Open":
Click on File/Save Project and close the Asset Exporter. Now the asset files are included into the project.
To apply the texture amd the model to our scene, lets modify first the brick BasicMaterial by modifying the line in CreateBox() method:
csharp code:
By getting the brickTexture texture
csharp code:
Finally, modify the line:
csharp code:
By getting the brick texture:
csharp code:
Final result: