FootyVolley gameplay tuning – Aiming

 estimated time : 12 mn
ingredients needed : Unity, Playmaker
(this recipe is part of the footvolley game menu )

Detailed Steps

 

Showing/Hiding an aim on command.
Place an aim at random point in a chosen region.
Controlling the aim.

So far in our game, our player stops moving when we press our kick button; and a circle collider appears and shrinks while we’re still hold our button.
The visual clue means that the more we wait at one place, the harder it’ll be to hit the ball before it reaches the ground.
All because of the limited size of our collision circle. The purpose is the challenge.
Forcing the player to anticipate the best position to be to sent back the ball.

To counter the negative effect of the immobility, we grants the player to increase the force of the kick when the ball will be intercepted (a power value that’ll increase while the collider shrinks). Now we’ll have to introduce a new challenge with positive/negative effects : the aim.

The aiming mechanic needs to have benefits and drawback:
– The aim appears at random
+ We aim where we want, the longer we press the shot button, the better we aim where we want.

With all these mechanics rules in mind, we can now implement them.

00:01 Creating aim sprite

Create a GameObject called aim as a child of the Player1 object because our aim position will be dependant of our player position.

Add a sprite object to it, we can use our collider sprite and rotate it by 90° on its X axis to be displayed correctly on our 2.5D plane.

Set its order layer to the character layers as we’re above the ground display, then move it to the opposite field, because we want our aim to appears in the opposite side.

00:42  Hiding aim object

On our Player1 FSM , we go back to our Idle state in order to disable the aim while idle.
We disable it by adding again the action:“activate game object” (either by copy/paste the precedent action, or by adding it) and choosing our aim object as the object to disable.activate aim sprite and its controller when pressing shoot

01:17  Showing aim object

activate aim sprite and its controller when pressing shoot.
Make a complete new state called”init Aim”, we’ll position it between our “Idle state” and our “isKicking” state.

In order to do that, we have to link our Idle kickPressed transition event to our new “initAim” state.
Just drag and drop the arrow going from the “kickPressed” event of our “Idle” state to “isKicking” state, and we make it go from “Idle” to our new “initAim” state.

Now we’ll make our aim visible again when entering our new state, by adding the action Activate game Object, and specifying our “aim” gameobject as the object to be activated.
Add a Finished transition on our aim and link it to the “isKicking” state.

02:16 Init aim in random pos

In Playercontroller script we’ll add an aimReset() function that’ll be called from our “init Aim” state.
The script will place at random the aim.

We need to know the field coordinates in order to constrain the aim to randomly appears into the field.

First set up our field dimensions :We create two gameObjects TerrainR & TerrainL, with box collider of the size of the game field.
Warning : when playing with the dimensions of the Fields to be adjusted to our field sprite, be sure to be playing with the BoxCollider size and not the scale ! as we’re later use math based on the size of these fields it will results in strange behaviors ( finding a width between 0 and 200, isn’t exactly the same thing than finding a width between 0 and 1 with the scale of 200).

Then, we create a gameController class that will contain our main variables available from any other classes.

After creating the GameController, attach the script to the main camera, then we add  in our PlayerController the code to access the game controller in our start function :

//declaration
private GameManager gameManager;
 
void Start() {
//..various init
gameManager = GameManager.Instance();
}

02:57 Finding our field dimensions

Back in our GameManager script we retrieve our field dimensions :

 

//declaration as boxcollider
public BoxCollider terrainR,terrainL;
 
//in Awake() function
terrainR = GameObject.Find ("TerrainR").GetComponent<BoxCollider> ();
terrainL = GameObject.Find ("TerrainL").GetComponent<BoxCollider> ();
In order to init our aim at a random position, we first have to retrieve its coords,
so we create a new variable in our player FSM : a vector3 var called Aim_pos that’ll store the aim transform value.
And once it’s done we retrieve it into our playerController script :
//declare a FsmVector value, to retrieve our aim position stored as a FsmVector
private FsmVector3 Aim_pos;
 
void Awake(){
      //here retrieve our Fsm component
      MyFsm = this.GetComponent<PlayMakerFSM>();
   }
void Start(){
//..previous code..
//here the code to retrieve Fsm Global vars from script, in this case our Aim_pos value
Aim_pos= MyFsm.FsmVariables.FindFsmVector3("Aim_pos");
}

04:59 Resetting aim position

We’d like now for the sake of our gameplay to show the aim cursor at a random position on the opponent field. In order to do that, we’ll use our boxCollider values of the fields TerrainL and TerrainR (now you remember how bad I am with variable names).

So we’ll create a function in our PlayerController script that will be called from our Player FSM “Init Aim” state. Basically it’ll tells each time we make the aim appeared; it will appear into random values clamped between X and Z min and max values .
Z axis in our case instead of Y because we’re in 2.5D.

Let’s talk about how choose these values; the xmin value don’t exactly match with the transform position of the field; because we’ll make appear an aim sprite object that got a certain width.
And if by misfortune (and god knows how unlucky part-time coders are 😉 ) our random value is set to 0; as the anchor point of the sprite is set at its center, our aim will be half in part of the opponent field and the other half in our field.
In order to avoid that we add the aim width to the math, simple.

a little explicative sketch, drawn with all my heart

As we need the aim width value, we declare it upfront in our gameManager.

I’ve made it manually because I know the sprite’s value, but you can retrieve it dynamically of course :

[HideInInspector]
public float aimWidth= 0.4f;

A quick note about the [HideInInspector] code

We need our aim width to be public, because we’d like to get the information from other classes. But as I don’t want to see tons of useless variables (useless because they’ll be init by code at start) in my inspector I prefer to hide them from the inspector with the [HideInInspector] directive. just a personal conception.

 

We now include the resetAimPos() function in our playerController script :

//before that we declare the var that will be used:
private float xmin,xmax,zmin,zmax,aimWidth;
 
void resetAimPos(){
//here retrieve our aimWidth
aimWidth = gameManager.aimWidth;
 
//we retrieve the transform position of the field stored in our gameManager and add or remove the aim with to it.
xmin =(gameManager.terrainR.transform.position.x-(gameManager.terrainR.size.x/2))+aimWidth;
xmax= gameManager.terrainR.transform.position.x+(gameManager.terrainR.size.x/2)-aimWidth;
zmin = ((gameManager.terrainR.size.z/2)*-1)+aimWidth;
zmax=(gameManager.terrainR.size.z/2)-aimWidth;
 
//create a new vector with random clamped values
Vector3 pos = new Vector3 (Random.Range (xmin, xmax), gameManager.terrainR.transform.position.y, Random.Range (zmin, zmax));
 
//then change the value of the FSM Aim_pos vector
Aim_pos.Value = pos;
 
}

Now we go back in our Player FSM, and in our “Init Aim” state, we’ll add a FSM action : “Send Message”, that will call our resetAimPos() function that will set the new Aim_pos value, and finally we add another FSM action: “Set position” and choose our aim gameObject to have the Aim_pos new vector change.

08:41 Controlling the aim

In order to control the aim, we go in Player1’s “IsKicking” state FSM; as we’re moving the aim only when kickbutton’s pressed.
We’ll add FSM actions to control the aim.

Before that we create a AimMovement vector 3 in our playmaker variables, because we’ll need to store the aim position in order to translate (move) our aim later.

Back in “IsKicking” state.
Add the GetAxisVector action
: we use the horizontal & vertical axis as inputs to control our aim.
We let the map to plane as XZ and we store the vector in our AimMovement vector created earlier. We set the multiplier to 2 to quicken a little our aim speed.

Add the Translate action: we specify that we want to move (translate) the aim object along the AimMovement vector.

10:11 Storing the aim position

Then add another action : Get Position.
It stores into a vector the transform position of a chosen object. We specify the aim gameObject, and set the values to be stored at our Aim_pos value. We choose the world coordinates, and check the “everyFrame” box.We’ll use it to know where to sent the ball, if we succeed do kick it back.

Right now, if you play, you’ll be able to move your aim freely. Too freely even though.
You can sent it away of the field and make it disappears in the void of our scene.

To prevent the aim for exiting the field; we add to the aim GameObject a boxCollider and a rigidBody to enable collision with the walls. Uncheck the “use gravity” for the rigidBody.
To avoid any strange rotations of the aim while colliding, we freeze the rotations on all axis, and freeze the Y position  in the rigidBody Constraints. 

 

 

 

Leave a Reply