If you followed the previous steps of this tutorial, you should have a "game" with a menu and a scene containing an object that can move in four directions. You should also know how to handle the mouse and other input, move objects ... now, let's see how to handle object orientation and how to spawn new objects in our game !
Change character orientation[]
The goal of this part will be to point our character toward the player's mouse.
To do that, We need to determine the point at which we want to direct our object. Remember that we are in 3D space while the mouse moves in a 2D space (the screen), but we have already solved this kind of problem in the previous tutorial, with a ray and the IntersectModelRenderer function we ca know whether or not the cursor is on a particular model.
We will use the same kind of technique here : we will launch a ray from our camera through the mouse position and seek for its intersection with the ground, which will give us the point to which turn our object.
As usual, all ray methods are defined in the scripting reference. We will use:
[number or nil] Ray:IntersectsPlane( [Plane] plane )
Let's start by creating our ray from our camera. We define a public variable in the Awake() to store the component of our Camera object:
self.cameraComponent = CraftStudio.FindGameObject( "Camera" ):GetComponent( "Camera" )
Then in update(), we retrieve the mouse position and we create our ray :
local mousePos = CraftStudio.Input.GetMousePosition()
local mouseRay = self.cameraComponent:CreateRay( mousePos )
We are missing the argument to pass to our function mouseRay:IntersectsPlane(). It must be a plane : Planes, as vectors, are mathematical objects (so they have no representation on the screen). According to the scripting reference (), a plane is created by:
[Plane] Plane:New( [Vector3] normal, [number] distance )
Our plane will be all the point with same height (position along the Y axis) Y=-1. So the normal vector will be along the Y axis : (0,1,0), we can dircetly get it with Vector3:Up() . Then we define our plane in Awake() with :
self.groundPlane = Plane:New( Vector3:Up(), -1 )
We get the distance between the camera's center and the intersection between the ray and the plane with :
local distance = mouseRay:IntersectsPlane( self.groundPlane )
From this distance, we ca calculate the intersection point with:
local targetPos = mouseRay.position + mouseRay.direction * distance
Look out, when there is no intersection between the ray and the plan, distance will be nil. It could cause some errors, so we will add a test before using the variable distance :
if distance ~= nil then local targetPos = mouseRay.position + mouseRay.direction * distance end
If you can check if our point is good, just create a cross hair model and add it in the scene then add :
self.CrossHair = CraftStudio.FindGameObject( "CrossHair" ) in Awake()
self.'CrossHair '.transform:SetPosition( targetPos ) in the last if.
Run your scene, and you must see your cross hair follow the mouse. You can adjust his height by tweequing the definition of self.groundPlane.
Then, we just need to turn our object toward the target :
self.gameObject.transform:LookAt( targetPos )
If you
If test test, you should realize that the whole thing is difficult to control. In fact, the camera is linked to our object, it rotates at the same time as him. In your scene, disassociate the camera and the object. Then, with all you have learned so far, you should be able to create a script for your camera to follow your subject. (Or just keep the camera stationary, as you want)
Script complet :
function Behavior:Awake()
self.vitesse = 0.05
self.groundPlane = Plane:New( Vector3:Up(), -1 )
self.cameraComponent = CraftStudio.FindGameObject( "Camera" ):GetComponent( "Camera" )
self.CrossHair = CraftStudio.FindGameObject( "CrossHair" )
end
function Behavior:Update()
--gestion des déplacements
local horizontal = CraftStudio.Input.GetAxisValue( "Horizontal" )
local vertical = CraftStudio.Input.GetAxisValue( "Vertical" )
local mouvement = Vector3:New( horizontal, 0, -vertical )
mouvement = mouvement * self.vitesse
self.gameObject.transform:Move (mouvement)
--gestion de l'orientation
local mousePos = CraftStudio.Input.GetMousePosition()
local mouseRay = self.cameraComponent:CreateRay( mousePos )
local distance = mouseRay:IntersectsPlane( self.groundPlane )
if distance ~= nil then
local targetPos = mouseRay.position + mouseRay.direction * distance
self.CrossHair.transform:SetPosition( targetPos ) --optional
self.gameObject.transform:LookAt( targetPos )
end
end
Throw projectiles
Now we will allow our object to throw some bullets.
In our player management script, we can start defining when the projectiles will be created: when the user click. By default, the left button is linked to the input "Fire" (you can check the properties of your project). Let's add a new condition in Update():
if CraftStudio.Input.IsButtonDown( "Fire" ) then end
We know need to know what we ahve to put in our condition to create our bullet.
The simplest technique is to define a bullet in a prefabricated scene. Then, from our script in the main scene, we will add ("instantiate") a new object created from our prefabricated bullet scene. This way, we can create as many new object as we want, all bullet will be independent and all copies of the original object.
Start creating a bullet model (a small cube of color, for example), then create a new scene. Name it "prefab_bullet." The prefix "prefab_" will differentiate your game scenes and those used to define prefabricated elements. In this scene, add an object to which you associate your model and a new script (named "bullet" for example). This script will define the behavior of our projectile.
For now we just want it to go straight ahead. We will use the method transform:MoveOriented() to be sure it always moves straight ahead. We obtain:
function Behavior:Awake()
self.speed = 0.5
end
function Behavior:Update()
self.gameObject.transform:MoveOriented( Vector3:Forward()*self.speed )
end
Now, in the script that handles our character, add a line that creates a new object constructed from our prefabricated scene. We will use the function:CraftStudio.Instantiate ([string] name [table] scene)
The name of our new object does not matter, call it "bullet". We aslo have to retreive our prefabricated scene, so, In Awake() add:
self.bulletPrefab = CraftStudio.FindAsset( "prefab_bullet" )
Our line to create a new bullet will be :
CraftStudio.Instantiate("bullet",self.bulletPrefab)
If you try, you will see your newly created projectiles appear anywhere (at the main scene origin in fact) and always go in the same direction. We have to change define spawning point and their orientation.
Back to the "bullet" script and add in awake():
self.source = CraftStudio.FindGameObject( "Cube" )
self.gameObject.transform:SetPosition (self.source.transform:GetPosition())
self.gameObject.transform:SetOrientation (self.source.transform:GetOrientation())
This way, each projectile, once created, will be placed at the center of our "Cube" object (my character, maybe you used a different name) and will take the same orientation.
There is one final problem: all these projectiles created will continue to exist and advance indefinitely. Ultimately this could cause us serious problems. We'll give them a "life time".
In Awake() of the "bullet" script , define a maximum life and a counter to know the age of our object:
self.MaxLife = 20 self.life = 0
Then, in Update(), add a line to age the bullet and a condition to destroy once he reach his maximum life time :
self.life = self.life +1 if self.life > self.MaxLife then CraftStudio.Destroy( self.gameObject ) End
Voila !
Final script :
Player's script
function Behavior:Awake() self.vitesse = 0.05 self.groundPlane = Plane:New( Vector3:Up(), -1 ) self.CrossHair = CraftStudio.FindGameObject( "viseur" ) self.cameraComponent = CraftStudio.FindGameObject( "Camera" ):GetComponent( "Camera" ) self.bulletPrefab = CraftStudio.FindAsset( "prefab_bullet" ) end function Behavior:Update() --handle movement local horizontal = CraftStudio.Input.GetAxisValue( "Horizontal" ) local vertical = CraftStudio.Input.GetAxisValue( "Vertical" ) local mouvement = Vector3:New( horizontal, 0, -vertical ) mouvement = mouvement * self.vitesse self.gameObject.transform:Move (mouvement) --handle orientation local mousePos = CraftStudio.Input.GetMousePosition() local mouseRay = self.cameraComponent:CreateRay( mousePos ) local distance = mouseRay:IntersectsPlane( self.groundPlane ) if distance ~= nil then local targetPos = mouseRay.position + mouseRay.direction * distance self.viseur.transform:SetPosition( targetPos ) self.gameObject.transform:LookAt( targetPos ) end --handle firing if CraftStudio.Input.IsButtonDown( "Fire" ) then CraftStudio.Instantiate("bullet",self.bulletPrefab) end end
Bullet script :
function Behavior:Awake() self.speed = 0.5 self.MaxLife = 20 self.source = CraftStudio.FindGameObject( "Cube" ) self.gameObject.transform:SetPosition (self.source.transform:GetPosition()) self.gameObject.transform:SetOrientation (self.source.transform:GetOrientation()) self.life = 0 end function Behavior:Update() self.life = self.life +1 self.gameObject.transform:MoveOriented( Vector3:Forward()*self.speed ) if self.life > self.MaxLife then CraftStudio.Destroy( self.gameObject ) end end