Mouse Click Move Terrain with Gravity?

Hello everyone. I have been trying to emulate the movement that many action / rpg’s use, such as diablo 2 , TQ, etc. There is many free scripts around the internet that offer click and move ability , but they all seem to a similiar problem that i have noticed. Click and moving across a planar platform ( a plane) , but all these scripts i have seen seem to have one common problem. They dont support moving off the planar position that the object (player) is on. In other words, when moving across terrain with heights, the object just ignores the height completely.

I did manage to find this script (as posted below) and it does almost everything i want it to, except… it ignores (technicly, does not apply) gravity when using the Move() function. So what happens is. now i can move the object around and it will climb hills properly, but it will not fall back down to the ground ( or stay grounded) when descending the hill.

I have tried to mix and merge a few scripts, write my own additions to make this work, but so far i have had zero luck. I am fairly new to this whole thing, so just wondering if anyone out there can show me , or give me a tip, so i could get this all working correctly. As i said, the following script does everything i want it to now, exceppt… theres no gravity, so it will not stay grounded after ascending the terrain.

var dir : Vector3;
var hit : RaycastHit ;
var rot = 0;
var k : Vector3;
var speed : float = 1.0;
var gravity = 1.0;

private var yourCharacterController : CharacterController;

function Start() {
  yourCharacterController = GetComponent(CharacterController);
  yourCharacterController.isTrigger = true;
}

function FixedUpdate()
{

var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(Camera.main.transform.position,ray.direction,hit,1000);

dir = hit.point - transform.position;
dir[1] = 0;

var angle = Vector3.Angle(dir, transform.forward);
var k = Vector3.Cross(transform.forward, dir);
k.Normalize();

rot = k[1]*(angle*2.5);

	if (Input.GetMouseButton(0)) 
	{
		transform.Rotate(Vector3.up*rot*Time.deltaTime);

		yourCharacterController.Move(dir.normalized * Time.deltaTime * speed);
	}
}

function Update()
{
/// here i have added a cheesy *wants to be gravity** but object just keeps falling through terrain and falling forever.
	transform.position.y -= gravity * Time.deltaTime;
}

I am wondering if possibly i can use the gravity as i have it, and use a trigger of some sort to detect when the object contacts the terrain when it descends? But i have been awake now for about 22 hours and am beginning to become very unfocused, and over stuffed in the head, hehe.

Thanks for any help guy , greatly appreciate it.

EDIT I have tried to add this collision event so i can create a boolean case of grounded or not, but when testing this simple script, it seems to never trigger/ This is attached to the terain, and i than applied a rigid body to my gameObject, but nothing happens. It still falls through the terrain, and no msg sems to be sent.

function OnCollisionEnter(object : Collision)
{
	print("Contact made");
}

Argh! BUMP!

Ok, so after another hour or three of playing around , i have managed to get gravity working, and the triggers detecting the events, but its still not working correctly.

It seems that as soon as i begin trying to move, the trigger exit event fires (not sure i understand why, im still standing on the terrain just fine) but as soon as i move, the trigger says not detecting collision, and i fall half way through the terrain, than i can not move at all.

So frustrated, so cose to working, and yet its not :frowning: hehe. Anyways , here is the slightly different code with the trigger events in place, and the character controller.isTrigger set to true.

var dir : Vector3;
var hit : RaycastHit ;
var rot = 0;
var k : Vector3;
var speed : float = 1.0;
var gravity = 1.0;
var grounded : boolean = false;

private var yourCharacterController : CharacterController;

function Start() {
  yourCharacterController = GetComponent(CharacterController);
  yourCharacterController.isTrigger = true;
}

function FixedUpdate()
{

var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(Camera.main.transform.position,ray.direction,hit,1000);

dir = hit.point - transform.position;
dir[1] = 0;

var angle = Vector3.Angle(dir, transform.forward);
var k = Vector3.Cross(transform.forward, dir);
k.Normalize();

rot = k[1]*(angle*2.5);

//if(grounded)
//{
	if (Input.GetMouseButton(0)) 
	{
		transform.Rotate(Vector3.up*rot*Time.deltaTime);

		yourCharacterController.Move(dir.normalized * Time.deltaTime * speed);
	}
//}
}

function Update()
{
	if(!grounded)
	{
		transform.position.y -= Time.deltaTime;
	}
}


function OnTriggerEnter(other : Collider)
{
	grounded = true;
	print("collision detected");
}

function OnTriggerStay(other : Collider)
{
	grounded = true;
	print("collision detected");
}

function OnTriggerExit(other : Collider)
{
	grounded = false;
	print("no collision atm");
}

SOLVED !! I Think.

Ok , i think i ahve gotten it working correctly (it seems to do exactly what i want in game so far with no noticeable bugs). Whether this is an optimal way to do this i have no idea, but here it is.

The following script moves the character around the terrain up and down hills.

var dir : Vector3;
var hit : RaycastHit ;
var rot = 0;
var k : Vector3;
var speed : float = 1.0;
var gravity = 1.0;
var grounded : boolean = false;

private var yourCharacterController : CharacterController;

function Start() {
  yourCharacterController = GetComponent(CharacterController);
  yourCharacterController.isTrigger = true;
}

function FixedUpdate()
{

var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(Camera.main.transform.position,ray.direction,hit,1000);

dir = hit.point - transform.position;
dir[1] = 0;

var angle = Vector3.Angle(dir, transform.forward);
var k = Vector3.Cross(transform.forward, dir);
k.Normalize();

rot = k[1]*(angle*2.5);

//if(grounded)
//{
	if (Input.GetMouseButton(0)) 
	{
		transform.Rotate(Vector3.up*rot*Time.deltaTime);

		yourCharacterController.Move(dir.normalized * Time.deltaTime * speed);
	}
//}
}

function Update()
{
	if(!grounded)
	{
		//transform.position.y -= Time.deltaTime;
		yourCharacterController.Move (Vector3(0, -gravity, 0));
		print("not grounded");
	}
}


function OnTriggerEnter(other : Collider)
{
	grounded = true;
	print("collision detected");
}

function OnTriggerStay(other : Collider)
{
	grounded = true;
	print("collision detected");
}

function OnTriggerExit(other : Collider)
{
	grounded = false;
	print("no collision atm");
}

So I’ve been looking into this too. It’s important in my project that I have terrain. I’ve tried a couple solutions such as ripping out the car tutorial locomotion and combining it with the AStar pathfinding project. That worked, but without reliably starting/stopping where I wanted the unit did.

Ultimately I found the RTS project here on the forums and used the tank movement (Vehicle.js, which was ideal). Below is my hacked version. All I do is pass in the waypoint direction from AIFollow.cs into “targetPosition”.

Hope this helps. It’s been a pain in the arse for a while for me. :wink:

private bool moveToTarget = false;
    public Vector3 targetPosition;
    private float rotationForce = 1.1f; // 2.5
    private float targetReachedRadius = 7.0f;
    private float maxEngineForce = 10.0f;// 14.0
    private float engineForce = 0.0f;
    private float pitchForce = 0.0f;
    private float accelerationForce = 6.0f; // 9.0
    private float brakeForce = 20.0f;
    private float distanceToDestination = 0.0f;
    private MoveModeType moveMode = MoveModeType.STOP;

    enum MoveModeType{ STOP = 1, FORWARD = 2};

    void Init()
    {
        StartCoroutine(UpdateMove());
    }

    IEnumerator UpdateMove()
    {
        while (true)
        {
            // check the distance
            yield return null;

            distanceToDestination = Vector3.Distance(targetPosition, transform.position);
            if (distanceToDestination < targetReachedRadius)
            {
                moveMode = MoveModeType.STOP;
                moveToTarget = false;
                distanceToDestination = 0.0f;
            }
            else
                moveToTarget = true;

            if (moveToTarget)
            {
                Quaternion rotation = Quaternion.LookRotation(targetPosition - transform.position);
                float str = Mathf.Min(rotationForce * Time.deltaTime, 1);
                transform.rotation = Quaternion.Lerp(transform.rotation, rotation, str);

                // Check direction angle.
                // If greater than 60° then first turn without moving, otherwise full throttle ahead.
                var targetDir = targetPosition - transform.position;
                var forward = transform.forward;
                var angle = Vector3.Angle(targetDir, forward);

                if (angle > 60.0)
                    moveMode = MoveModeType.STOP;
                else
                    moveMode = MoveModeType.FORWARD;
            }
        }
    }
    void FixedUpdate()
    {
        switch (moveMode)
        {
            case MoveModeType.STOP:
                engineForce -= brakeForce * Time.deltaTime;
                if (engineForce < 0)
                    engineForce = 0;

                // no pitchforce if we stop!
                pitchForce = 0.0f;
                break;

            case MoveModeType.FORWARD:
                engineForce += accelerationForce * Time.deltaTime;
                if (engineForce > maxEngineForce)
                    engineForce = maxEngineForce;

                float pitchAngle = transform.localEulerAngles.x;
                if (pitchAngle > 180)
                {
                    // vehicle's nose is up, value is below 360°. Normalize to -x degree.
                    pitchAngle = -(360 - pitchAngle); // now normalized to (+/-)0-x degree
                }
                pitchForce = maxEngineForce * pitchAngle / 100.0f;
                break;
        }
        transform.Translate(Vector3.forward * Time.deltaTime * (engineForce + pitchForce));
    }

Hi Wyliam.

That script you wrote - does that just drop into a new project, or does it rely on other scripts as well?

I am working on developing an RPG framework on my game, and starting working on unity friday night. So far I have fudged together a semi working click to move script but it isnt ideal.

I intend to merge it with the locomotion system and a decent camera system, but having trouble.

Will that script work on its own as a character controller or does it refer to other scripts/includes as well?

Just quickly reading over it, the script looks as if it can be used stand alone, i do not see it directly referencing anything externally. Although , Wyliam will know best.

Yeah… I tested it last night (as a javascript) and got a bunch of ridiculous compile errors that didnt make sense (missing ; on line 1, when there clearly was one there… expected function but got bool - what?)

So yeah, for my, with unity 3, it isnt working at least.

-Ash

Its in C#

Hello again everyone. I am having one problem with this script. Its been quite some time since i used it, and now i have reason to need it again, and it is working fine , except , that i wish to have it work in a multi tasking sort of sense. What i would like to do , is be able to hold the button down and drive the object around like is possible in this current state, but i also want to aquire a target position when the button being clicked, so that if the user lets go of the mouse button, the object will continue to move until it reaches the target position.

I figured i would try it out by just calculating the length between the target position , and the transform.position, but it doesnt seem to be working for me. Can anyone help me out a lil , what am i missing. Heres what i have so far.

var speed : float = 5.0;
var turnSpeed : float = 2.0;
var gravity = 9.87;

private var dir : Vector3;
private var hit : RaycastHit ;
private var rot = 0;
private var k : Vector3;
private var grounded : boolean = false;
private var yourCharacterController : CharacterController;

private var targetPosition : Vector3;

function Start() {
  yourCharacterController = GetComponent(CharacterController);
  yourCharacterController.isTrigger = true;
}

function FixedUpdate()
{
	if(Input.GetAxis("Horizontal")){
		transform.Rotate(0, Input.GetAxis("Horizontal") * turnSpeed, 0);
	}

	//capture the screen to world raycast hit
	var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

	// Raycast(Vector3 : origin , Vector3 : direction, out HitInfo : RaycastHit, distance : float) 
	Physics.Raycast(Camera.main.transform.position, ray.direction, hit, 1000);

	dir = hit.point - transform.position;
	dir[1] = 0;

	var angle = Vector3.Angle(dir, transform.forward);
	var k = Vector3.Cross(transform.forward, dir);
	k.Normalize();

	rot = k[1]*(angle*2.5);

	if (Input.GetMouseButton(0)) 
	{
		targetPosition = hit.point - transform.position;
		transform.Rotate(Vector3.up*rot*Time.deltaTime);
		yourCharacterController.Move(dir.normalized * Time.deltaTime * speed);
	}
	else{
		transform.Rotate(Vector3.up*rot*Time.deltaTime);
		if((targetPosition - transform.position).magnitude > 2){
			yourCharacterController.Move(targetPosition.normalized * Time.deltaTime * speed);
		}
	}

	if(!grounded)
	{
		yourCharacterController.Move (Vector3(0, -gravity, 0));
	}

}

Bump, anyone , prolly something simple i missed , but gotta go to work now.

change

Input.GetMouseButton(0)

to

Input.GetMouseButtonDown(0)

Atleast im pretty sure itll work, should check for the mouse down on each frame and update the targetPosition accordingly.

Hi, I realize its been quite sometime but I am just wondering if anyone figured this out? Being able to click to set the character’s target position and have it keep moving until it gets there with the mouse not being held down.

I m completly new with this and looking for a skript like this thats working with unity 5. Someone an idea? i get dozens of errors with that one :confused: