- Home /
Make an NPC follow player on collision, ala Yoshi collecting eggs.
I am working on a mini 2D platformer game and I have a general understanding of C# and how the syntax works but I am essentially a Unity noob. I have scoured this site and the net for answers/tutorials on how to do this but I haven't found anything that works for me yet. What I want to do, is have my player collect NPCs in the same fashion that Yoshi collects eggs and they follow him around.
The way I have set it up so far (which I am totally willing to change to a more efficient method if there is one), is when my player collides with the NPC, it enables the NPC's HingeJoint2D component. I originally used the SpringJoint2D, but I don't want the NPC to orbit around my player, I just want them follow, like they are running behind. I've got everything working and I'm fairly happy with the result but currently, the NPCs follow while looping their idle animation. I want their walk animation to play when they are following, but I can't seem to figure out how to script that because all I can find is how to animate on an input/getkey.
How can I get the NPCs to animate based on the horizontal movement that is the effect of the physics of the HingeJoint2D? I set the transitions up in the Animator, but that doesn't seem to take care of it, so I assume I need some script that I just don't currently understand. Maybe I am going about this the wrong way, if so, any guidance would be greatly appreciated. I've found a few follow scripts but none seem to work the way I want them to. I am attaching my player controller script (swapped out my object names with Player and NPC1,2,3 for easy reference) below so you can see exactly what I am working with. I do not currently have any script attached to my NPCs.
public class PlayercontrollerScript : MonoBehaviour
{
public float maxSpeed = 10f;
bool facingRight = true;
Animator anim;
bool grounded = false;
public Transform groundCheck;
float groundRadius = 0.2f;
public LayerMask whatisGround;
public float jumpForce = 700f;
bool doubleJump = false;
public GameObject NPC1;
public GameObject NPC2;
public GameObject NPC3;
public HingeJoint2D NPC1Follow;
public HingeJoint2D NPC2Follow;
public HingeJoint2D NPC3Follow;
public PolygonCollider2D NPC1Pass;
public PolygonCollider2D NPC2Pass;
public PolygonCollider2D NPC3Pass;
void Start()
{
anim = GetComponent<Animator>();
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("PickUpNPC1"))
{
NPC1Follow.enabled = true;
NPC1Pass.enabled = false;
}
if (other.gameObject.CompareTag("PickUpNPC2"))
{
NPC2Follow.enabled = true;
NPC2Pass.enabled = false;
}
if (other.gameObject.CompareTag("PickUpNPC3"))
{
NPC3Follow.enabled = true;
NPC3Pass.enabled = false;
}
}
void FixedUpdate()
{
grounded = Physics2D.OverlapCircle(groundCheck.position, groundRadius, whatisGround);
anim.SetBool("Ground", grounded);
if (grounded)
doubleJump = false;
float move = Input.GetAxis("Horizontal");
anim.SetFloat("Speed", Mathf.Abs(move));
anim.SetFloat("vSpeed", GetComponent<Rigidbody2D>().velocity.y);
GetComponent<Rigidbody2D>().velocity = new Vector2(move * maxSpeed, GetComponent<Rigidbody2D>().velocity.y);
if (move > 0 && !facingRight)
Flip();
else if (move < 0 && facingRight)
Flip();
}
void Update()
{
if ((grounded || !doubleJump) && Input.GetKeyDown(KeyCode.Space))
{
anim.SetBool("Ground", false);
GetComponent<Rigidbody2D>().AddForce(new Vector2(0, jumpForce));
if (!doubleJump && !grounded)
doubleJump = true;
}
}
void Flip()
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
Answer by IsaiahKelly · May 08, 2016 at 12:42 AM
How can I get the NPCs to animate based on the horizontal movement that is the effect of the physics of the HingeJoint2D?
Just pass the horizontal speed (aka velocity) of the NPC's rigidbody to the animator. The player control script already gives us some hints on how we might do this:
float move = Input.GetAxis("Horizontal");
anim.SetFloat("Speed", Mathf.Abs(move));
anim.SetFloat("vSpeed", GetComponent<Rigidbody2D>().velocity.y);
We can simply change the part of the code above that sets the animator "Speed" float to the rigidbody's absolute (Mathf.Abs) x velocity, rather than the horizontal input, like this:
anim.SetFloat("Speed", Mathf.Abs(GetComponent<Rigidbody2D>().velocity.x));
anim.SetFloat("vSpeed", GetComponent<Rigidbody2D>().velocity.y);
However, GetComponent is slow, so calling it every update is a bad idea. It's much better to do any GetComponent calls on Start or Awake, and cache the reference for later use instead.
I would also recommend separating your logic for the NPC collecting behavior into another script. Ideally, each script should only be responsible for one task. This will make your code much more module and maintainable.
First off, delete all the NPC collecting logic from your player control script. Then create a new script to handle just this task using the code below.
For this example I'm just calling these NPC things "things" :) I'm also assuming you don't actually need three separate unique references to the NPCs? If you do, you can always add that logic back, but you shouldn't need any joint references since they're being created on-the-fly for you here.
using UnityEngine;
using System.Collections.Generic;
public class CollectThings : MonoBehaviour
{
// Tag for things we want to collect.
public string thingTag = "NPC";
// Maximum amount of things we can collect.
public int maxThings = 3;
// Connected joint damping amount.
public float followDamping = 5;
// Fixed follow distance. 0.5 seems good.
public float followDistance = 0;
// List to hold all collected things.
private List<GameObject> collected;
// List to hold all joints.
private List<SpringJoint2D> joints;
void Start()
{
// Initialize lists. Makes sure they're not null and empty.
collected = new List<GameObject>();
joints = new List<SpringJoint2D>();
}
void Update()
{
// Testing
if (Input.GetKeyDown(KeyCode.E))
{
RemoveAll();
}
}
void OnTriggerEnter2D(Collider2D other)
{
// Is this something we want to collect?
if (other.gameObject.CompareTag(thingTag))
{
// If already at maximum limit, abort.
if (collected.Count >= maxThings)
return;
AddThing(other.gameObject);
}
}
void AddThing(GameObject obj)
{
// Add a joint to this object.
var j = gameObject.AddComponent<SpringJoint2D>();
// Connect thing to the joint and set joint settings.
j.autoConfigureConnectedAnchor = false;
j.dampingRatio = followDamping;
j.connectedBody = obj.GetComponent<Rigidbody2D>();
// Use fixed follow distance if set.
if (followDistance > 0)
{
j.autoConfigureDistance = false;
j.distance = followDistance;
}
// Add new joint to the joints list.
joints.Add(j);
// Add new collected thing to collection list.
collected.Add(obj);
}
// Free all collected things by destroying their joints.
void RemoveAll()
{
// Destroy all joints.
foreach (var joint in joints)
{
Destroy(joint);
}
// Remove all collected things from the list.
collected.Clear();
}
}
I used Spring joints here because hinge joints don't allow the NPCs to follow at varying distances, but you can change that if you want. To prevent the spring joints from bouncing all over the place you just need to increase the NPC's rigid-body linear damping value to something like 1. You should also probably decrease their mass or increase the player's mass, to prevent them from pulling the player around. However, this is all stuff you'll need to tweak how you like it.
Oh, and you probably don't need to disable colliders on the collected NPCs because collisions between jointed objects are disabled automatically.
Thanks so much! This is all so incredibly helpful. High five!!!!!
I should probably also point out that the "collected" list in my example script is not actually used for anything and could be deleted. However, it may be useful to keep it there if you want to extend the script in the future.
You could, for example, change the collected list type from GameObject to the NPC's AI script or something, then use GetComponent to grab a reference to each collected NPC's AI when you pick them up and add it to the list. Allowing you to later on trigger something on all collected NPC AI by iterating through the list of collected AI, etc.
Additionally, if you needed to check if a specific NPC was collected, you could check the collected GameObject's name, rather than creating a bunch of extra tags for each, like this:
// Did we collect NPC2?
private bool gotNPC2 = false;
void OnTriggerEnter2D(Collider2D other)
{
// Is this object named NPC2?
if (other.gameObject.name == "NPC2")
{
// We got NPC!
gotNPC2 = true;
}
}
Your answer
Follow this Question
Related Questions
[Unity 4.3] 2D physics - SpeedUp Platform/Slope [C#] 0 Answers
HOLD THE JUMP BUTTON TO JUMP HIGHER 0 Answers
Using animated transform to push object away? 2D 0 Answers
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers