What happened to the Slender Guide by alucardj

How to make a Survival Horror game like Slender - Slenderman and Collecting Papers

Hi. Im a big fan of the horror survival game genre, especially the still popular Slender game.

Is there a guide that can help me write my own horror survival game.

I have seen a guide by Jay Kay (alucardj) , but I really need help with understanding how to :

  • Have a first person character controller on a terrain
  • set the ambience of the scene
  • Be able to raycast to pick up objects (like papers)
  • Have some type of enemy with some simple AI
  • Have interaction between the character and enemy scripts
  • Simply load different scenes
  • include running and having different footstep sounds for walking and running
  • have a full screen effect like the static in Slender

Well Jay, you should know, as you (that’s me!) just finished making a video series of me following my own guide starting with a blank project, so everyone can follow along and see why it works for me and not other people. Some people like myself are visual and aural people, who need a demonstration in more than one dimension to help you understand where you may be going wrong with the guide.

As well as the videos, I am starting a Unity Forum page, where people can come together and help each other with their Survival Horror genre games.

The original guide has nearly 17 000 views, and it keeps growing. With different comments posted over 2 of my main answers, and the explosion of answers to questions that came after my packages were made availabe, it is now almost impossible to follow in a linear way.

So those questions will be closed now, and I shall Not be responding to any more posts there. I am going to post all the latest scripts here, but then am not going to answer any comments on this question either. All future traffic to my ‘Slender’ Guide is going to be solely through the forum page. Any questions that appear on Unity Answers will also be closed an redirected to the forum page.

While this may seem a little unfair, it is actually a good thing for all of your benefit. Every comment, change and update will appear there, for everyone to read and contribute to. No more searching through different questions just to get different parts of functionality, but everything you could need for starting your own horror survival game will be in one place. Doesn’t that sound convenient ?!

But just to start off, and as promised in the videos, I am posting all of the latest scripts here with a link to the Forum page and the Videos. After that, this is the very last time, I shall not respond to any question that has my scripts appearing on Unity Answers. Instead you will feel my wrath for not being able to follow a simple instruction. If you have a problem with one of my scripts, #only ask on the Forum Page# . That is all. I hope everyone (that can happily follow this rule) learns something, expands their knowledge, and begins their own adventure in learning to code and write logic for games.

Have Fun, and Happy Coding.


This is now the only place I shall write answers to problems with my scripts : http://forum.unity3d.com/threads/172415-Slender-Guide-by-alucardj


Here is the body of my guide in written form (link to another post for now with scripts removed) : http://answers.unity3d.com/questions/296068/how-to-make-a-slender-man-follow-character-script.html


Now here are all my current scripts. These are the only scripts I will be posting and keeping up to date. Basically, these are the latest and greatest !

#Do Not post a new question on Unity Answers with my scripts#

Simply go to the forum thread, and then ask anything you like =]

Here it is again : http://forum.unity3d.com/threads/172415-Slender-Guide-by-alucardj

#If you post a new question on Unity Answers with my scripts, I shall bring the hammer down !#

Fair warning, ok. now …

All Current Slender Scripts




Collect Papers - CollectPapers.js

#pragma strict
@script RequireComponent( AudioSource )
 
var papers : int = 0;
var papersToWin : int = 8;
var distanceToPaper : float = 2.5;
 
public var paperPickup : AudioClip;
 
var theEnemy : EnemyScript;
 
 
function Start()
{
	Screen.lockCursor = true;
	 
	// find and store a reference to the enemy script (to reduce distance after each paper is collected)
	if ( theEnemy == null )
	{
		var nme : GameObject = GameObject.Find( "Enemy" );
		
		if ( nme )
		{
			theEnemy = nme.GetComponent( EnemyScript );
		}
	}
}
 
 
function Update()
{
	//if ( Input.GetMouseButtonUp(0) ) // use E in editor as LockCursor breaks with left mouseclick
	if ( Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.E) )
	{
		//var ray = Camera.main.ScreenPointToRay( Input.mousePosition ); // always cast ray from center of screen
		var ray = Camera.main.ScreenPointToRay( Vector3( Screen.width * 0.5, Screen.height * 0.5, 0.0 ) );
		var hit : RaycastHit;
		if ( Physics.Raycast( ray, hit, distanceToPaper ) )
		{
			//if ( hit.collider.gameObject.tag == "Paper" )
			if ( hit.collider.gameObject.name == "Paper" )
			{
				papers += 1;
				//Debug.Log( "A paper was picked up. Total papers = " + papers );
				 
				audio.PlayClipAtPoint( paperPickup, transform.position );
				 
				Destroy( hit.collider.gameObject );
				 
				// make enemy follow closer
				//theEnemy.ReduceDistance();
				
				// make enemy follow closer
				if ( papers == 1 )
				{
				    theEnemy.SetFirstPaperDistance();
				}
				else
				{
				    theEnemy.ReduceDistance();
				}
			}
		}
	}
}
 
 
function OnGUI()
{
	if ( papers < papersToWin )
	{
		GUI.Box( Rect( (Screen.width * 0.5) - 60, 10, 120, 25 ), "" + papers.ToString() + " Papers" );
	}
	else
	{
		GUI.Box( Rect( (Screen.width/2)-100, 10, 200, 35 ), "All Papers Collected!" );
		Application.LoadLevel( "SceneWin" );
	}
}



Enemy Script - EnemyScript.js

#pragma strict
@script RequireComponent( AudioSource )
 
public var thePlayer : Transform;
private var theEnemy : Transform;
 
public var speed : float = 5.0;
 
var isOffScreen : boolean = false;
public var offscreenDotRange : float = 0.7;
 
var isVisible : boolean = false;
public var visibleDotRange : float = 0.8; // ** between 0.75 and 0.85 (originally 0.8172719)
 
var isInRange : boolean = false;
 
public var followDistance : float = 24.0;
public var maxVisibleDistance : float = 25.0;
 
public var reduceDistAmt : float = 3.1;

public var startingDistance : float = 2000.0;
private var firstPaperDistance : float = 24.0;
 
private var sqrDist : float = 0.0;
 
public var health : float = 100.0;
public var damage : float = 20.0;
 
public var enemySightedSFX : AudioClip;
 
private var hasPlayedSeenSound : boolean = false;
 
private var colDist : float = 5.0; // raycast distance in front of enemy when checking for obstacles
 
 
function Start()
{
	if ( thePlayer == null )
	{
		thePlayer = GameObject.Find( "Player" ).transform;
	}
	 
	theEnemy = transform;
	
	firstPaperDistance = followDistance;
	followDistance = startingDistance;
	
	InvokeRepeating( "TeleportEnemy", 1, 10 );
}
 
function Update()
{
	// Movement : check if out-of-view, then move
	CheckIfOffScreen();
	 
	// if is Off Screen, move
	if ( isOffScreen )
	{
		MoveEnemy();
		 
		// restore health
		RestoreHealth();
	}
	else
	{
		// check if Player is seen
		CheckIfVisible();
		 
		if ( isVisible )
		{
			// deduct health
			DeductHealth();
			 
			// stop moving
			StopEnemy();
			 
			// play sound only when the Man is first sighted
			if ( !hasPlayedSeenSound )
			{
				audio.PlayClipAtPoint( enemySightedSFX, thePlayer.position );
			}
			hasPlayedSeenSound = true; // sound has now played
		}
		else
		{
			// check max range
			CheckMaxVisibleRange();
			 
			// if far away then move, else stop
			if ( !isInRange )
			{
				MoveEnemy();
			}
			else
			{
				StopEnemy();
			}
			 
			// reset hasPlayedSeenSound for next time isVisible first occurs
			hasPlayedSeenSound = false;
		}
	}
	 
}
 
 
function DeductHealth()
{
	// deduct health
	health -= damage * Time.deltaTime;
	 
	// check if no health left
	if ( health <= 0.0 )
	{
		health = 0.0;
		Debug.Log( "YOU ARE OUT OF HEALTH !" );
		 
		// Restart game here!
		Application.LoadLevel( "SceneLose" );
	}
}
 
 
function RestoreHealth()
{
	// deduct health
	health += damage * Time.deltaTime;
	 
	// check if no health left
	if ( health >= 100.0 )
	{
		health = 100.0;
		//Debug.Log( "HEALTH is FULL" );
	}
}
 
 
function CheckIfOffScreen()
{
	var fwd : Vector3 = thePlayer.forward.normalized;
	var other : Vector3 = (theEnemy.position - thePlayer.position).normalized;
	 
	var theProduct : float = Vector3.Dot( fwd, other );
	 
	if ( theProduct < offscreenDotRange )
	{
		isOffScreen = true;
	}
	else
	{
		isOffScreen = false;
	}
}
 
 
function MoveEnemy()
{
	// Check the Follow Distance
	CheckDistance();
	 
	// if not too close, move
	if ( !isInRange )
	{
		rigidbody.velocity = Vector3( 0, rigidbody.velocity.y, 0 ); // maintain gravity
		 
		// --
		// Old Movement
		//transform.LookAt( thePlayer );
		//transform.position += transform.forward * speed * Time.deltaTime;
		// --
		 
		// New Movement - with obstacle avoidance
		var dir : Vector3 = ( thePlayer.position - theEnemy.position ).normalized;
		var hit : RaycastHit;
		 
		if ( Physics.Raycast( theEnemy.position, theEnemy.forward, hit, colDist ) )
		{
			//Debug.Log( " obstacle ray hit " + hit.collider.gameObject.name );
			if ( hit.collider.gameObject.name != "Player" && hit.collider.gameObject.name != "Terrain" )
			{
				dir += hit.normal * 20;
			}
		}
		 
		var rot : Quaternion = Quaternion.LookRotation( dir );
		 
		theEnemy.rotation = Quaternion.Slerp( theEnemy.rotation, rot, Time.deltaTime );
		theEnemy.position += theEnemy.forward * speed * Time.deltaTime;
		 
		// --
	}
	else
	{
		StopEnemy();
	}
}
 
 
function StopEnemy()
{
	transform.LookAt( thePlayer );
	 
	rigidbody.velocity = Vector3.zero;
}
 
 
function CheckIfVisible()
{
	var fwd : Vector3 = thePlayer.forward.normalized;
	var other : Vector3 = ( theEnemy.position - thePlayer.position ).normalized;
	 
	var theProduct : float = Vector3.Dot( fwd, other );
	 
	if ( theProduct > visibleDotRange )
	{
		// Check the Max Distance
		CheckMaxVisibleRange();
		 
		if ( isInRange )
		{
			// Linecast to check for occlusion
			var hit : RaycastHit;
			 
			if ( Physics.Linecast( theEnemy.position + (Vector3.up * 1.75) + theEnemy.forward, thePlayer.position, hit ) )
			{
				Debug.Log( "Enemy sees " + hit.collider.gameObject.name );
				 
				if ( hit.collider.gameObject.name == "Player" )
				{
					isVisible = true;
				}
			}
		}
		else
		{
			isVisible = false;
		}
	}
	else
	{
		isVisible = false;
	}
}
 
 
function CheckDistance()
{
	var sqrDist : float = (theEnemy.position - thePlayer.position).sqrMagnitude;
	var sqrFollowDist : float = followDistance * followDistance;
	 
	if ( sqrDist < sqrFollowDist )
	{
		isInRange = true;
	}
	else
	{
		isInRange = false;
	}
}
 
 
function ReduceDistance()
{
	followDistance -= reduceDistAmt;
}
 
 
function CheckMaxVisibleRange()
{
	var sqrDist : float = (theEnemy.position - thePlayer.position).sqrMagnitude;
	var sqrMaxDist : float = maxVisibleDistance * maxVisibleDistance;
	 
	if ( sqrDist < sqrMaxDist )
	{
		isInRange = true;
	}
	else
	{
		isInRange = false;
	}
}


function SetFirstPaperDistance() 
{
    followDistance = firstPaperDistance;
}


function TeleportEnemy() 
{
    // Movement : check if out-of-view, then move
    CheckIfOffScreen();

    // if is Off Screen, check for Teleport
    if ( isOffScreen )
    {
       // Check the Follow Distance
       CheckDistance();

       // if not too close, Teleport
       if ( !isInRange )
       {
         // determine a position to teleport to
         var teleportDistance : float = 50.0; // teleport 50 units to the right of the player (thePlayer.right)
         
         var rndPos : int = -1;
         
         if ( Random.Range( 0, 2 ) == 1 )
         {
         	rndPos = 1;
         }
         
         
         var newPos : Vector3 = thePlayer.position + ( rndPos * thePlayer.right * teleportDistance );
         
         newPos.y = 1000.0;

         // raycast to that position
         var hit : RaycastHit;

         if ( Physics.Raycast( newPos, -Vector3.up, hit, 1000.0 ) )
         {
          // check if it hit the terrain
          if ( hit.collider.gameObject.name == "Terrain" )
          {
              // move the enemy to the new position (add a little to the y so it doesn't fall through)
              theEnemy.position = hit.point + ( Vector3.up * 0.5 );
              theEnemy.LookAt( thePlayer );
          }
         }
       }
    }
}


 
 
function OnGUI()
{
	GUI.Box( Rect( (Screen.width * 0.5) - 60, Screen.height - 35, 120, 25 ), "Health : " + parseInt( health ).ToString() );
}



Flickering Flashlight - FlickeringFlashlight.js

#pragma strict
 
var minWorkingTime : float = 60.0;
var maxWorkingTime : float = 120.0;
 
var minLightIntensity : float = 0.5;
var maxLightIntensity : float = 2.5;
 
private var startIntensity : float = 1.85;
 
enum flashlightState { IsWorking, Flickering, Resetting }
 
var currentState : flashlightState;
 
private var workingTimer : float = 0.0;
private var workingTimeLimit : float = 90.0;
 
var minFlickerSpeed : float = 0.05;
var maxFlickerSpeed : float = 0.2;
 
private var flickerCounter : float = 0.0;
 
private var resetTimer : float = 0.0;
 
function Start()
{
    workingTimeLimit = Random.Range( minWorkingTime, maxWorkingTime );
     
    light.enabled = true;
    startIntensity = light.intensity;
     
    currentState = flashlightState.IsWorking;
}
 
function Update()
{
	switch( currentState )
	{
		case flashlightState.IsWorking :
			IsWorking();
		break;
		 
		case flashlightState.Flickering :
			FlickerFlashlight();
			CheckForInput();
		break;
		 
		case flashlightState.Resetting :
			Resetting();
		break;
	}
}
 
function IsWorking()
{
	workingTimer += Time.deltaTime;
	 
	if ( workingTimer > workingTimeLimit )
	{
		flickerCounter = Time.time + Random.Range( minFlickerSpeed, maxFlickerSpeed );
		 
		currentState = flashlightState.Flickering;
	}
}
 
function FlickerFlashlight()
{
	if ( flickerCounter < Time.time )
	{
		if (light.enabled)
		{
			light.enabled = false;
		}
		else
		{
			light.enabled = true;
			 
			light.intensity = Random.Range(minLightIntensity, maxLightIntensity);
		}
		 
		flickerCounter = Time.time + Random.Range( minFlickerSpeed, maxFlickerSpeed );
	}
}
 
function CheckForInput()
{
	if (Input.GetKeyDown(KeyCode.F))
	{
		currentState = flashlightState.Resetting;
	}
}
 
function Resetting()
{
	resetTimer += Time.deltaTime;
	 
	if ( resetTimer > 0.75 )
	{
		resetTimer = 0.0;
		workingTimer = 0.0;
		workingTimeLimit = Random.Range( minWorkingTime, maxWorkingTime );
		light.enabled = true;
		light.intensity = startIntensity;
		currentState = flashlightState.IsWorking;
	}
	else if ( resetTimer > 0.65 )
	{
		light.enabled = false;
	}
	else if ( resetTimer > 0.55 )
	{
		light.enabled = true;
		light.intensity = startIntensity;
	}
	else if ( resetTimer > 0.25 )
	{
		light.enabled = false;
	}
	else if ( resetTimer > 0.15 )
	{
		light.enabled = true;
		light.intensity = startIntensity;
	}
	else if ( resetTimer > 0.05 )
	{
		light.enabled = false;
	}
}



Footstep Script - FootstepScript.js

#pragma strict
@script RequireComponent( AudioSource )
 
var walk : AudioClip;
var run : AudioClip;
 
var walkAudioSpeed : float = 0.4;
var runAudioSpeed : float = 0.2;
 
private var walkAudioTimer : float = 0.0;
private var runAudioTimer : float = 0.0;
 
var isWalking : boolean = false;
var isRunning : boolean = false;
 
var walkSpeed: float = 7; // regular speed
var runSpeed: float = 20; // run speed
 
private var chCtrl: CharacterController;
private var chMotor: CharacterMotor;
 
function Start()
{
chCtrl = GetComponent(CharacterController);
chMotor = GetComponent(CharacterMotor);
}
 
function Update()
{
	SetSpeed();
	 
	if ( chCtrl.isGrounded )
	{
		PlayFootsteps();
	}
	else
	{
		walkAudioTimer = 0.0;
		runAudioTimer = 0.0;
	}
}
 
function SetSpeed()
{
	var speed = walkSpeed;
	 
	if ( chCtrl.isGrounded && Input.GetKey("left shift") || Input.GetKey("right shift") )
	{
		speed = runSpeed;
	}
	 
	chMotor.movement.maxForwardSpeed = speed;
}
 
function PlayFootsteps()
{
	if ( Input.GetAxis( "Horizontal" ) || Input.GetAxis( "Vertical" ) )
	{
		if ( Input.GetKey( "left shift" ) || Input.GetKey( "right shift" ) )
		{
			// Running
			isWalking = false;
			isRunning = true;
		}
		else
		{
			// Walking
			isWalking = true;
			isRunning = false;
		}
	}
	else
	{
		// Stopped
		isWalking = false;
		isRunning = false;
	}
	 
	// Play Audio
	if ( isWalking )
	{
		if ( audio.clip != walk )
		{
			audio.Stop();
			audio.clip = walk;
		}
		 
		//if ( !audio.isPlaying )
		if ( walkAudioTimer > walkAudioSpeed )
		{
			audio.Stop();
			audio.Play();
			walkAudioTimer = 0.0;
		}
	}
	else if ( isRunning )
	{
		if ( audio.clip != run )
		{
			audio.Stop();
			audio.clip = run;
		}
		 
		//if ( !audio.isPlaying )
		if ( runAudioTimer > runAudioSpeed )
		{
			audio.Stop();
			audio.Play();
			runAudioTimer = 0.0;
		}
	}
	else
	{
		audio.Stop();
	}
	 
	// increment timers
	walkAudioTimer += Time.deltaTime;
	runAudioTimer += Time.deltaTime;
}


Scene Lose - SceneLose.js

#pragma strict

function Start() 
{
	Screen.lockCursor = false;
}

function OnGUI() 
{
	if ( GUI.Button( Rect( (Screen.width * 0.5) - 75, 10, 150, 50 ), "Lost Your Mind" ) )
	{
		Application.LoadLevel( "SceneMainMenu" );
	}
}


Scene Main Menu - SceneMainMenu.js

#pragma strict

function Start() 
{
	Screen.lockCursor = false;
}

function OnGUI() 
{
	if ( GUI.Button( Rect( (Screen.width * 0.5) - 75, 10, 150, 50 ), "Play Me !" ) )
	{
		Application.LoadLevel( "Level0" );
	}
}


Scene Win - SceneWin.js

#pragma strict

function Start() 
{
	Screen.lockCursor = false;
}

function OnGUI() 
{
	if ( GUI.Button( Rect( (Screen.width * 0.5) - 75, 10, 150, 50 ), "You Won !" ) )
	{
		Application.LoadLevel( "SceneMainMenu" );
	}
}


**Static Script** - StaticScript.js

// Static Script
// Nov 9 2012
// Jay Kay (alucardj)
// This script creates a full screen static effect

#pragma strict
@script RequireComponent(MeshFilter, MeshRenderer)

var theAlpha : float = 0.0;

var theCamera : Camera;
var cameraTransform : Transform;

private var mesh : Mesh;

private var uv : Vector2[];
private var verts : Vector3[];
private var tris : int[];
private var normals : Vector3[];

public var distance : float = 1.0;

private var theMaterial : Material;

var theEnemy : EnemyScript;


function Start()
{
	Startup();
	
	// find and store a reference to the enemy script (to use health as alpha for texture)
	if ( theEnemy == null )
	{
		theEnemy = GameObject.Find( "Enemy" ).GetComponent( EnemyScript );
	}
}

function Update()
{
	SetAlpha();
	
	ScrollUVs();
}


function SetAlpha()
{
	theAlpha = ( 100.0 - theEnemy.health ) * 0.01;
	
	theMaterial.color = Color( theMaterial.color.r, theMaterial.color.g, theMaterial.color.b, theAlpha );
}


function ScrollUVs()
{
	var scrollX : float = Random.Range( -0.5, 0.5 );
	var scrollY : float = Random.Range( -0.5, 0.5 );
	
	// UVs
	for ( var i:int = 0; i < 4; i ++ )
	{
		uv <em>= new Vector2( uv_.x + scrollX, uv*.y + scrollY );*_</em>

* }*

* mesh.uv = uv;*
}

// ----

function Startup()
{
* if ( theCamera == null )*
* {*
* theCamera = Camera.main;*
* }*
* cameraTransform = theCamera.transform;*

* theMaterial = gameObject.renderer.material;*
* theMaterial.color = Color.white;*

* if ( !mesh )*
* {*
* GetComponent(MeshFilter).mesh = mesh = new Mesh();*
* mesh.name = “ScreenMesh”;*
* }*

* Construct();*

* //DebugVerts();*
}

function Construct()
{
* mesh.Clear();*

* verts = new Vector3[4];*
* uv = new Vector2[4];*
* tris = new int[6];*
* normals = new Vector3[4];*

* // calculate verts based on camera FOV*
* var pos : Vector3 = cameraTransform.position - transform.position;*

_ var halfFOV : float = ( theCamera.fieldOfView * 0.5 ) * Mathf.Deg2Rad;
* var aspect : float = theCamera.aspect;
//Debug.Log( " Screen.width " + Screen.width + " : Screen.height " + Screen.height + " : aspect " + aspect );*_

_ var height : float = distance * Mathf.Tan( halfFOV );
var width : float = height * aspect;_

* //Debug.Log( " fieldOfView " + theCamera.fieldOfView + " : aspect " + aspect );*

* // UpperLeft*
_ verts[0] = pos - (cameraTransform.right * width);
verts[0] += cameraTransform.up * height;
verts[0] += cameraTransform.forward * distance;_

* // UpperRight*
_ verts[1] = pos + (cameraTransform.right * width);
verts[1] += cameraTransform.up * height;
verts[1] += cameraTransform.forward * distance;_

* // LowerLeft*
_ verts[2] = pos - (cameraTransform.right * width);
verts[2] -= cameraTransform.up * height;
verts[2] += cameraTransform.forward * distance;_

* // LowerRight*
_ verts[3] = pos + (cameraTransform.right * width);
verts[3] -= cameraTransform.up * height;
verts[3] += cameraTransform.forward * distance;_

* // UVs*
* uv[0] = new Vector2( 0.0, 1.0 );*
* uv[1] = new Vector2( 1.0, 1.0 );*
* uv[2] = new Vector2( 0.0, 0.0 );*
* uv[3] = new Vector2( 1.0, 0.0 );*

* // Triangles*
* tris[0] = 0;*
* tris[1] = 1;*
* tris[2] = 2;*
* tris[3] = 2;*
* tris[4] = 1;*
* tris[5] = 3;*

* // Normals*
* normals[0] = -Vector3.forward;*
* normals[1] = -Vector3.forward;*
* normals[2] = -Vector3.forward;*
* normals[3] = -Vector3.forward;*

* // assign mesh*
* mesh.vertices = verts;*
* mesh.uv = uv;*
* mesh.triangles = tris;*
* mesh.normals = normals;*

* mesh.RecalculateBounds();*
* mesh.RecalculateNormals();*
}

/*
function DebugVerts()
{
* // Debug Positions*
* Debug.Log( " UL " + verts[0] + " : UR " + verts[1] );*
* Debug.Log( " LL " + verts[2] + " : LR " + verts[3] );*
}
*/
------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------