Footy volley gameplay tuning- part 5

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

Detailed Steps

Create a new FSM in the ball gameObject.
Calls a function when a global event is fired.
Find ball and aiming coordinates.
Create a moving trajectory function for the ball.
Playing with heights and speed within the ball trajectory.

 

We will now stall a little on the gameplay’s core : the ball.

00:05 Creating the soccer ball FSM 5s

Create a new FSM into the Soccer Ball gameObject, add a new global transition and let’s call it “Service”. It’s a blank state that do nothing, and it will be our starting state as well.

The service state is called when starting the game, when a player (or CPU) will serve (hence the name “Service”, see? I’m not that bad with names finally).

But from the moment where a player will kick the ball, this will call a global event, that’ll change the state of the ball and start the ball movement.

Making changes in the ball

We will treat the ball as an independent player. Instead of calling functions from player and CPU that will direct the ball, we will send global events and the ball will respond to them.

The ball will dictate the game which player won the point by telling when it touches the ground.

00:19 Sending the Global PlayerKick Event

We first create a global event called “PlayerKick”, that will be used to tell the ball that the Player kicked the ball.

Note:
The “Player” does not only refer to human player, but also the CPU.

We go onto our t_collider gameObject, and on the “Add force”, we first delete our “add force” action that we used for our first prototype and add a FSM action  instead : “Send Event”.
Let’s review a the “Send Event” a little deeper;

First we change the Event target, to be a “gameObject FSM”. It means that this event will be visible not into our t_collider object, but into another gameObject that got a FSM : our “Soccer Ball” gameObject. We type our default FSM name : “FSM”, then we choose the Event to be fired, so we fire the “PlayerKick” event.

 

01:19 Listening to the PlayerKick Event

In our soccer ball FSM, we create a new state : “Kicked”; This state will set up the launching step of our ball.
Right away, we send a message and call for a function that will do the setup :”SettingUp”.

In this step, we will set up the starting pos of the ball, and initiate the ball movement within a script in the Soccer Ball gameObject. Let’s start by create a new script into our Soccer ball gameObject : “ball.cs”.

Wait, before diving into more code, we forgot a little step first :
Our event “PlayerKick” was fired, so we have to listen it first; and it will be this event that will help us to enter into the “Kicked” event.

So, just add a global transition “playerKicked” in our “Kicked” state of our soccer ball gameObject.

 

02:25 Getting our Starting Position

We need of some variables to start move : the starting point & the ending point.
The starting point will be the position of the ball.

So we need two vectors, that we declare in our ball.cs, along with other declaration well known if you’ve followed the tutorial from the first steps:

 

//..along with other namespaces declarations
using HutongGames.PlayMaker;
//..
 
//declarations within the ball class
private GameManager gameManager;
FsmVector3 start_pos,end_pos;
PlayMakerFSM MyFsm;
//..
 
 
void Awake(){
gameManager = GameManager.Instance();
MyFsm = this.GetComponent<PlayMakerFSM>();
}
 
//this is the first function called by the player 
//when kicking the ball
void SettingUp(){
//getting the start coord( ball coordinates)
start_pos = this.transform.position;
}

Just pause a second, because we need to change a thing, in order to retrieve our end_pos; which is the position of our aim.

In our GameManager.cs we create public vector ballAim

//again we hide the value in the inspector to not be confused 
//and be tempted to add a value ourselves
[HideInInspector] 
	public Vector3 ballAim;

We pass the actual value of our aim vector in the Player1 FSM, “release Kick” state; by adding the “send message” action, using the method name “saveAim” (which is the name of  function that we will create in a few steps), and passing the vector3 parameter : “Aim_pos”.

Now let’s create the saveAim function, go into our playerController script

void saveAim(Vector3 pos){
//here we pass the passed value to the gameManager
	gameManager.ballAim = pos;
}

07:11 Shooting the Ball

Now we’re going to kick our ball from it’s starting point to the aiming point.

When we release our kick button, we store the aiming position in our gameManager “ballAim” vector.
So we’re going to add some code in our Soccer ball’s ball script, to make our ball travel from point A to point B with a little vector based Lerp function (Based on official Unity teaching of Lerp).

//initialize our new needed vars
private float startTime;
private float journeyLength;
bool isRolling =false;
 
void SettingUp(){
  start_pos = this.transform.position;
  //* new */
  //here we set our ending position
  end_pos = gameManager.ballAim;
 
  //here's the initialisation to the lerp
  startTime = Time.time;
  journeyLength = Vector3.Distance(start_pos, end_pos);
  //we use a boolean to enter in the update loop only when we're rolling to the ending position
  isRolling =true;
}
 
//on each frame if we're rolling, we update the ball position.
void Update() {
  if(isRolling){
    //here's the lerp in action
    float distCovered = (Time.time - startTime) * speed;
    float fracJourney = distCovered / journeyLength;
    transform.position = Vector3.Lerp(start_pos, end_pos, fracJourney);
  }
}

 

08:00 Giving elevation to the ball 8m

Ok, we’ve seen our ball moving; so what? it’s not really sexy, isn’t it?
Well let’s give some elevation by shooting our ball a little higher with the help of a Quadratic bezier curve function.

The bezier curve need a few variables :

  • A starting and ending point, we basically got them : the ball position and the aim position.
  • A control point : let’s say it’s the top of the curve. I’ve arbitrarily choose 6 as the Y value for this one, but we’ll change this later.

And for the math explanations, as i’m not really into it, here’s a technical explanation for those who wants to dig deeper.

let’s create our new ShootingBall() function, that will initialize our ball movement

 

void ShootingBall(Vector3 startPoint,Vector3 endPoint){
  //the bezierTime is basically the length of the journey , 
  //0 is our starting point, and if bezierTime reaches 1 
  bezierTime = 0;
  startPointX = startPoint.x ;
  startPointY = startPoint.y;
  startPointZ = startPoint.z;
 
  endPointX = endPoint.x;
  endPointY = endPoint.y;
  endPointZ = endPoint.z;
 
  controlPointX = (endPointX-startPointX)/4;
  controlPointY = 6;
  controlPointZ = (endPointZ-startPointZ)/4;
 //yes, our ball will be moving now
 isRolling = true;
}

Changeons un peu la fonction Update() de notre ball.cs

void Update(){
 if (isRolling){
//1 = POW value (to change)
  bezierTime = bezierTime + Time.deltaTime + (1/100);
  //if ball reached destination
  if (bezierTime >=1)
    isRolling = false;
  //here's where the magic happens
  curveX = (((1-bezierTime)*(1-bezierTime)) * startPointX) + (2 * bezierTime * (1- bezierTime) * controlPointX) + ((bezierTime * bezierTime) * endPointX);
  curveY = (((1-bezierTime)*(1-bezierTime)) * startPointY) + (2 * bezierTime * (1- bezierTime) * controlPointY) + ((bezierTime * bezierTime) * endPointY);
  curveZ = (((1-bezierTime)*(1-bezierTime)) * startPointZ) + (2 * bezierTime * (1- bezierTime) * controlPointZ) + ((bezierTime * bezierTime) * endPointZ);
  //applying the Bezier math current results to our position
  transform.position = new Vector3(curveX,curveY,curveZ);
 }
}

of course, don’t forget about our declarations :

float bezierTime,curveX,curveY,curveZ;
float startPointX,startPointY,startPointZ;
float endPointX,endPointY,endPointZ;
float controlPointX,controlPointY,controlPointZ;

Now we just make a call to our ShootingBall() function, in our ball.cs at the end of our settingUp() (don’t forget to remove the previous lerp vars):

void SettingUp(){
 start_pos = this.transform.position;
 //here we set our ending position
 end_pos = gameManager.ballAim;
 
 ShootingBall(start_pos,end_pos);
}

and now let’s test !

Whaaaaat ?
Oh, okay; I get it. O
ur bezier curve finishes a little bit under our plane, and as our ball use gravity nothing is here anymore to stop it from falling.
Just make a little tweak here (although not really accurate because if the ball reaches the ground, the point is finished anyway), in our ball.cs Update() function:

void Update(){
 
  //if ball reached destination
  if (bezierTime >=1){
    isRolling = false;
    //here we set the Y pos of the ball above the plane
    transform.position = new Vector3(curveX,0.1f,curveZ);
    //we exit the update function now, in order to not execute
    //the curveY calculations above, that will make our ball falls
    return;
 
  }

Now if we test, the ball stays on the ground.

 

08:46 Playing with heights

 

What’s the benefit of reducing our player collider again? gain more power.
So we’ll now adjust power and height of the ball , the height will be important later on, when playing with the future net in the center of the field.

We first create the power and scale variables in our gameManager.cs

 

[HideInInspector]
public float power,aimScale;

then we go in our playerController script and create 2 functions that will set the previous gameManager variables

void savePower(float pow){
  gameManager.power = pow;
}
void saveScale(float scale){
  gameManager.aimScale = scale;
}

and in our Player1 FSM, we add 2 send Message actions in our releaseKick state :

-one that will call the savePower method, passing a float parameter : “curr_power”.
-one that will call the saveScale method, passing a float parameter : “curr_scale”.

Now that our variables are known, we’ll first play with the heights, remember in bll.cs, the number 6 wisely chosen ?
It’s time to do it properly, first let’s declare a var in our ball script that we will be displayed in our editor, to allow us to tune it fine :

public int curveCoeff = 2;

then we change a thing in our ShootingBall function, where there was the line

controlPointY = 6;

change it to:

controlPointY = curveCoeff + gameManager.aimScale;

making the ball go higher if we do not hold too long our kick button.

 

13:10 And playing with power

 

We will change in our ball script the setup of our bezierTime in the update() function like this :

bezierTime = bezierTime + Time.deltaTime +(gameManager.power);  //0,04 speed max

and our gameManager.power need to get a value between 0 and 0,04. So we got 2 ways to do this :

-the simple one, as we got in our visual editor on a powerUnit value to edit in our playerController script, just play with it a little and we find that the value : 0.0005, fit perfecty if you just press the power will be equals to 0,001, and if you hold when the circle stops shrinking you’ll get about 0.04. we will use this.

-the other one is to use a function that clamps and interprets your values; let’s say your max value will be between 0,2 and 26. And your needed values must be between 0 and 0,04. Then the function will convert the real values into wanted values. I’ll make a post later on showing the function.

So for the moment just set the power unit value to 0.0005 in the editor and we’ll be fine.

A little collision trick

We want to avoid collisions between the player and the ball, as they both have rigidBodies & colliders.
It can create troubles when we’ll try to receive the ball, the ball can bumps on the head of the player ruining the basic gameplay.

we’ll cancel the collision detection with a little script : AvoidCollisionByTag.

This script allow the gameObject to ignore physic collision detection between the gameObject and a selected Tag, in our case we will avoid collision with the “Player” tag.
So just select the “Player” tag on our player gameObject, drag & drop the AvoidCollisionByTag script on the SoccerBall gameObject, and fill in the editor the wanted tag to avoid :”Player”.

The way the character controller works (including a capsule collider) forces us to add another a capsule collider to the player too.
So just add a rigidbody to the player gameObject, set its Drag value to 1000 (adding air resistance prevents the player to be moved by other gameObjects like radius collider); do not use gravity.
And add a capsule collider with a radius of 0.1 and we’re fine.
Like this our ball shouldn’t bounce on the player.

 

Leave a Reply