Drawing into the game - Line Rider style

I am making a 2D platformer with help from the 2D platformer tutorial, and am trying to set up a drawing mechanic where the player can draw a 2D platform using the mouse…

At the moment, I have it so that a sphere prefab is instantiated at the position of the mouse cursor when the left mouse button is pressed. What I want to do is to draw a line when the left mouse button is held down, which the player can then jump onto. Anyone know how I can do such a thing? I don’t have Unity Pro.

You should instantiate a series of platforms while the mouse button is held down. Assuming Unity properly polls the mouse from fixed update I would take the mouse position every x fixed updates and instantiate a platform.

If I remember right fixed update runs 20 times a second so 10 would be a good interval to poll the mouse position.

Here is a snippet of beam spawning code from the bridge building game I’m working on

// convert the start and end points to Vector3 and scale them
Vector3 start = new Vector3(0, -b.start.point.y, b.start.point.x)/100;
Vector3 end = new Vector3(0, -b.end.point.y, b.end.point.x)/100;

// calculate the angle between start and end
float _angle = Vector3.Angle(start - end, Vector3.forward);

// Vector3.Angle always returns a positive number, if start is above
// end then it needs to be negative
if(start.y > end.y) {_angle = -_angle;}

// create a quaternion from rotation around the x axis
Quaternion angle = Quaternion.AngleAxis(_angle, Vector3.right);

// find the midpoint between start and end
Vector3 position = Vector3.Lerp(start, end, .5f);

// the scale on the z axis is equal to the length of the beam
Vector3 scale = new Vector3(1, 1, Vector3.Distance(start, end));


// spawn and apply the scale
GameObject go = Instantiate(toInstantiate, position, 
                            angle) as GameObject;
go.transform.localScale = scale;
go.transform.parent = bridgeParent.transform;

// assing the new GameObject to the beam, this is used for assinging 
// hinge joints on the BeamJoints
b.gameObject = go;

b is the beam to spawn, b.start.point and b.end.point are the Vector2’s representing the start and end points, respectively.

Another way to go, and the one I followed, was to respond to the end draw event by creating a mesh on the fly using vertices built from properly-spaced points pulled from the line.

That way the mesh collider properly follows the actual mesh created by the drawing rather than trying to combine multiple other colliders that are fitted to primitives or other meshes not generated by the user’s line drawing.

The Unity procedural tutorials will provide the code to create a mesh if you need that part (that’s how I picked it up), but if I recall correctly they regenerate the mesh every Update or FixedUpdate, whereas I think you really need to do it only once, after the user’s line is complete, rather than spin trying to update the mesh every frame.

So I guess there are multiple ways to skin this particular cat. If you’re interested in this approach and need further pointers, I’ll post some code when I can.

Good luck!
Sean

Thanks for the replies.

I was just wondering. Could there be a way to instantiate a prefab, and then make it so that another of that prefab gets instantiated when the mouse position is x pixels away from the last placed prefab? This would make it so that prefabs wouldnt keep being instantiated even if the mouse wasn’t moving.

I’ll also take a look at both of your ideas if I have any trouble.

Stick the last used position in a variable and as you get new positions, check the magnitude difference between them and if it’s greater than a certain minimum value, make the new position the last used position and use it but otherwise ignore it; repeat until done.

You need to do this with the custom mesh approach, too, so you aren’t generating a large number of vertices.

I got the code to instantiate the prefabs, but still having trouble getting it to instantiate 1 prefab every 5 units away from the last.

The code I have at the moment is

var PenMark : Transform;
public var InkLevel = 100;
var mousePos : Vector3;
var Draw : boolean = true;
var worldPos;


function FixedUpdate () {

	//Checks to see if Mouse Button 1 is Down
    if (Input.GetMouseButton (0)  InkLevel > 0)
        DrawLine();
	
	//Restores ink if ink level is empty
	 if (Input.GetMouseButton (1)  InkLevel == 0)
	 InkLevel = 100;
}

function DrawLine() {

if (Draw){

	mousePos = Input.mousePosition;
	
	//Compensate distance from camera
	mousePos.z = 200;
	worldPos = camera.ScreenToWorldPoint(mousePos);
    var PenMarkClone : Transform = Instantiate(PenMark, worldPos, Quaternion.identity);
	InkLevel -= 1;
	Draw = false;
	}

if (Vector3.Distance(Input.mousePosition, mousePos) > 5)
	{
	Draw = true;
	}
}

The problem is with the DrawLine function, but I’m not quite sure how to programme it, since it still instantiates the PenMark prefab even when the mouse position is not changing.

Anyone know? I just want it so that the code above will not instantiate another prefab unless the distance away from the previous prefab is 5 units.

You could add a condition to your statement like;

//Get new positioning of the mouse
mousePos.z = 200;
var newPos = camera.ScreenToWorldPoint(mousePos);

//Then, make sure that the new position is greater by 5 or 
//whatever increment you want. So you could do something like
//this;
var difference : float = Vector3.Distance(newPos, worldPos); 
//the order of the vectors doesn't matter

//Checks to see if Mouse Button 1 is Down 
    if (Input.GetMouseButton (0)  InkLevel > 0  difference >= 5) 
        DrawLine();

Of course this doesn’t take in consideration the direction you came from. So you might have to do a bit of testing.

Thanks for the reply,

The main problem was that I was comparing the distance of the mouse position and not the mouse’s position in world space. I kinda got it working now, but the problem is that it evenly spaces the prefabs when you draw slow but when you draw fast, the prefabs cannot be instantiated fast enough and so there are big gaps in the line. Is there a way that the prefabs can be instatiated constatly ignoring the framerate of the game?

I know that some people will use Time.Deltatime to be independent of the frame rate because each computer are different.

The other thing you might want to look into to is that once the GameObject get’s instantiated, scale it so that it covers the distance of your mouse. Something like that might do the trick.

Hi Deikkan

Have you tried TronTrails.js it generates a mesh line. I have tried to adapt it for my purposes and it draws a line on game objects that have a collider called “collider” on them. My problem is that the line is continuous and connects the end of the line with the beginning of the next line. Anyway here is the code if it’s of some use to you. Let me know if you manage to resolve the continuity problem.

/*
 Generates a trail that is always facing upwards using the scriptable mesh interface.
 vertex colors and uv's are generated similar to the builtin Trail Renderer.
 To use it
 1. create an empty game object
 2. attach this script and a MeshRenderer
 3. Then assign a particle material to the mesh renderer
*/
var height = 2.0;//controls pen thickness
var alwaysUp = false;//flat line in which direction (for me it's left)
var minDistance = 1;//rough vs. smooth line

class TronTrailSection
{
	var point : Vector3;
	var upDir : Vector3;
}

private var sections = new Array();

function Update () {
	if (Input.GetKey(KeyCode.LeftAlt)) 
        {
        }
        else
        { 
    if (Input.GetMouseButton (0)) {
	  
	  var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
      var hit : RaycastHit; 
      Physics.Raycast (ray, hit);
      if(hit.collider.gameObject.tag.Contains("collider")) {
          
	  var position = hit.point + Vector3(0, 3, 0);

	// Add a new trail section
	if (sections.length == 0 || (sections[0].point - position).sqrMagnitude > minDistance * minDistance)
	{
		var section = TronTrailSection ();
		section.point = position;
		if (alwaysUp)
			section.upDir = Vector3.left;
		else
			section.upDir = transform.TransformDirection(Vector3.up);
		sections.Unshift(section);
	}
    // Rebuild the mesh
	var mesh : Mesh = GetComponent(MeshFilter).mesh;
	mesh.Clear();
	
	// We need at least 2 sections to create the line
	if (sections.length < 2)
		return;

	var vertices = new Vector3[sections.length * 2];
	var colors = new Color[sections.length * 2];
	var uv = new Vector2[sections.length * 2];
	
	var previousSection : TronTrailSection = sections[0];
	var currentSection : TronTrailSection = sections[0];

	// Use matrix instead of transform.TransformPoint for performance reasons
	var localSpaceTransform = transform.worldToLocalMatrix;

	// Generate vertex
	for (var i=0;i<sections.length;i++)
	{
		previousSection = currentSection;
		currentSection = sections[i];
				
		// Calculate upwards direction
		var upDir = currentSection.upDir;
		
		// Generate vertices
		vertices[i * 2 + 0] = localSpaceTransform.MultiplyPoint(currentSection.point);
		vertices[i * 2 + 1] = localSpaceTransform.MultiplyPoint(currentSection.point + upDir * height);		
	}

	// Generate triangles indices
	var triangles = new int[(sections.length - 1) * 2 * 3];
	for (i=0;i<triangles.length / 6;i++)
	{
		triangles[i * 6 + 0] = i * 2;
		triangles[i * 6 + 1] = i * 2 + 1;
		triangles[i * 6 + 2] = i * 2 + 2;

		triangles[i * 6 + 3] = i * 2 + 2;
		triangles[i * 6 + 4] = i * 2 + 1;
		triangles[i * 6 + 5] = i * 2 + 3;
	}
	// Assign to mesh	
	mesh.vertices = vertices;
	mesh.colors = colors;
	mesh.uv = uv;
	mesh.triangles = triangles;
}
}
}
}
@script RequireComponent (MeshFilter)