I am a beginner in Unity developing a 2D top-down mobile game. I am trying to create an enemy movement script that mimics the pattern of the leech enemy below:
This enemy is constantly trying to move towards the player but even though it can move quite quickly, due to its momentum, you are able to kite it as it cannot make a sharp turn without first taking some time to build speed in another direction.
I have created a script for enemies to constantly be targeting the player based on the player's current position but it is too difficult to dodge my enemies as they are able to turn instantly when the player does and maintain a constant speed. I would like to balance them to be more like this leech enemy so the player can dodge them by taking advantage of the enemy's current momentum with proper timing. How can I create this momentum effect for my enemies?
If you're using Unity's physics here's a way to do this nicely:
Walkable.cs
Create a modular component that all walkable game objects will use. The purpose of the component is to keep the object moving in the specified direction while stabilising the forces. It uses the values you configure in the inspector for speed and force. It does the movement inside of FixedUpdate as physics movement require it.
public class Walkable : MonoBehaviour {
private const float ForcePower = 10f;
public new Rigidbody2D rigidbody;
public float speed = 2f;
public float force = 2f;
private Vector2 direction;
public void MoveTo (Vector2 direction) {
this.direction = direction;
}
public void Stop() {
MoveTo(Vector2.zero);
}
private void FixedUpdate() {
var desiredVelocity = direction * speed;
var deltaVelocity = desiredVelocity - rigidbody.velocity;
Vector3 moveForce = deltaVelocity * (force * ForcePower * Time.fixedDeltaTime);
rigidbody.AddForce(moveForce);
}
}
Character.cs
This is a simple example of character that will follow a target. Notice how all it's doing is passing the direction to the walkable from inside an Update function.
public class Character : MonoBehaviour {
public Transform target;
public Walkable walkable;
private void Update() {
var directionTowardsTarget = (target.position - this.transform.position).normalized;
walkable.MoveTo(directionTowardsTarget);
}
}
Configurations
By configuring move and force variables you can get a variety of movement styles, some that can move fast but take a long time to ramp up, some that ramp up fast but move slowly overall.
You can also play around with with mass and linear drag on the Rigidbody2D to get even more control over the movement style.
Related
I'm making a 3D Side-Scroll Platformer Game,
I have trouble with my character when it steps on the moving platform it will not come along on the platform. I want my character to stay on the moving platform so I think converting my Character Controller into Rigibody will help me,
I need help to give me ideas on how I can reuse my Character Controller Script in Rigibody. This is my code, how can I reuse this in Rigibody script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public CharacterController controller;
private Vector3 direction;
public float speed = 8;
public float jumpForce = 30;
public float gravity = -20;
public Transform groundCheck;
public LayerMask groundLayer;
public bool ableToMakeADoubleJump = true;
public Animator animator;
public Transform model;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (PlayerManager.gameOver)
{
//play death animation
animator.SetTrigger("die");
//disable the script
this.enabled = false;
}
float hInput = Input.GetAxis("Horizontal");
direction.x = hInput * speed;
animator.SetFloat("speed", Mathf.Abs(hInput));
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.15f, groundLayer);
animator.SetBool("isGrounded", isGrounded);
if (isGrounded)
{
//Jump Codes
ableToMakeADoubleJump = true;
if (Input.GetButtonDown("Jump"))
{
Jump();
}
}
else
{
direction.y += gravity * Time.deltaTime;
if (ableToMakeADoubleJump & Input.GetButtonDown("Jump"))
{
DoubleJump();
}
}
if (hInput != 0)
{
Quaternion newRoattion = Quaternion.LookRotation(new Vector3(hInput, 0, 0));
model.rotation = newRoattion;
}
//Move the player using the character controller
controller.Move(direction * Time.deltaTime);
}
private void DoubleJump()
{
//Double Jump Codes
animator.SetTrigger("doubleJump");
direction.y = jumpForce;
ableToMakeADoubleJump = false;
}
private void Jump()
{
direction.y = jumpForce;
}
}
I would not recommend switching between the two. It would get tricky, and think about it, you are alternating between two very different things. One is movement and one is physics.
However, I would reccomend adding to your current script so that the player would move with the moving platform.
There is a lot of stuff in this answer, so read the whole thing.
Btw, when I talk about velocity, in your case it is direction.
Since it seems like you know how to code pretty well, I won’t write out the script, rather tell you some physics ideas to get you going in the right direction.
The reason people can stand on a moving platform and not fall off is because of friction.
If you are standing on a gameObject with enough friction (you could add a physics material the gameObject you stand on and change friction there. Note that physics materials only work with rigidbodies, but you might want to use it to just read the value)
First of all, you are going to want to raycast down to obtain the object you are standing on. From there you can get the physics material from hit.collider.sharedMaterial (or any other hit. to obtain data about what object you are standing on.
If they friction is too low, just make the character slip off, like it was before (I assume)
If the friction is above a threshold, get the velocity from the object you are standing on. If it was a rigidbody, hit.rigidbody.velocity. If it is controlled by script, use hit.collider.gameObject.GetComponent<scriptname>().velocityvariablename This part is continued later on
This is not necessary but useful: You can think of this as grabbing on a rope. When you are grabbing on a slippery rope, and someone pulls it (Like tug of war), You won’t move because the rope will slide through your hands. If the rope had grip tape on it and someone pulled it, you would come with it because it has more friction. You can think of the platform the same way. Now on to the more complex part: When you grip a rope that is stationary, and someone pulls it, you come with it as its velocity changes. When the rope is already being pulled, so its velocity is not stationary and it is already something. You grab onto it and a similar thing happens. It is like you are becoming a part of that rope. Similar to how if you are running, the arms and legs and head is a part of you. If you lose grip, you are no longer a part of that body, like your arms falling off when running. In other words, you become part of the body when you attach yourself to it.
Bottom line:
Get the velocity of the platform and set platformVel to it, do not add that to velocity, rather do a seperate controller.Move(platformVel).
A small customization:
Vector3.Lerp the platformVel to 0, so it doesn’t change while on the platform, but gradually goes to (0,0,0) when you get off. This way, there is a little momentum maintained from standing on the platform.
Feel free to ask anything in the comments.
I am building a 3D maze that is moved by the player. I'm taking a "labyrinth" approach and having a small ball be maneuvered through the maze when the player moves the maze. The problem occurs when the player moves a wall that the ball is currently resting on in the direction of the ball. The ball passes through the wall and stops on the next available wall.
The maze uses a mesh collider and rigidbody and the ball is a sphere collider.
I have drastically increased the physics frame rate to no avail. Considering the complexity of the maze and the potentially large number of maze combinations I really don't want to attach simple colliders to every wall. Any tips, tricks, suggestions, comments, etc. would be appreciated.
Ball Script for continious rotation:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ballGravity : MonoBehaviour {
public Rigidbody m_Rigidbody;
public Vector3 m_EulerAngleVelocity;
// Use this for initialization
void Start () {
m_EulerAngleVelocity = new Vector3 (0, 100, 0);
m_Rigidbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void FixedUpdate () {
Quaternion deltaRotation = Quaternion.Euler (m_EulerAngleVelocity * Time.deltaTime);
m_Rigidbody.MoveRotation (m_Rigidbody.rotation * deltaRotation);
}
}
Maze Rotation Script:
using UnityEngine;
using System.Collections;
public class rotObj : MonoBehaviour
{
private float baseAngle = 0.0f;
float rotSpeed = 10;
void OnMouseDown(){
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
pos = Input.mousePosition - pos;
baseAngle = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
baseAngle -= Mathf.Atan2(transform.right.y, transform.right.x) *Mathf.Rad2Deg;
}
void OnMouseDrag(){
//float rotY = Input.GetAxis("Vertical")*rotSpeed*Mathf.Deg2Rad;
//gm.transform.Rotate(Vector3.right, rotY);
Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
pos = Input.mousePosition - pos;
float ang = Mathf.Atan2(pos.y, pos.x) *Mathf.Rad2Deg - baseAngle;
transform.rotation = Quaternion.AngleAxis(ang, Vector3.forward);
}
}
To get consistent physics state its best to move colliders within FixedUpdate - if you move colliders in a OnMouseXX stage of the pipeline (Consider consulting https://docs.unity3d.com/Manual/ExecutionOrder.html), you risk that the change might be missed next time FixedUpdate is called. I.e. your ball moves through the state of your scene based on how it was before you rotated the maze, and with some unfortunate timings its possible that collisions get missed.
If you already tried with a tighter timestep, consider queueuing the OnMouseDrag change, like store the resulting rotation in a variable, and apply it in FixedUpdate, so it is applied just in case for next physics computation.
you may also consider moving your ball at the same time as you rotate your maze, quickly moving a mesh collider against a much smaller rigidbody is a sure source of trouble. Can you move a camera instead ?
I have a simple movement that relvolves around moving foward depending on where I look. The camera is made to follow the player. The problem I am havaing is that whenever I hit an object, my character(along with the camera) start spinning crazily all over the place. (I am new to coding)
Here is the code to the movement of where my character is facing :
public class Player : MonoBehaviour {
public float movementSpeed = 10;
public float turningSpeed = 60;
public Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody> ();
}
void Update() {
float horizontal = Input.GetAxis("Mouse X") * turningSpeed *
Time.deltaTime;
transform.Rotate(0, horizontal,0);
float vertical = Input.GetAxis("Mouse Y")* turningSpeed * Time.deltaTime;
transform.Rotate(vertical, 0, 0);
float movement = Input.GetAxis("Foward") * movementSpeed *
Time.deltaTime;
transform.Translate(0, 0,movement);
}
}
(Sorry for bad format)
The Mouse X just makes it so that it rotates on the x axis of mouse(same with y axis). The Foward is just the vertical input preset in unity.
Here is the code to the lookAt player :
public class LookAtCamera : MonoBehaviour {
public GameObject target;
void LateUpdate() {
transform.LookAt(target.transform);
}
}
Answer
I believe your rigidbody is being more heavily affected than what you desire by the bounce force occuring as you hit the object.
Possible solutions
Depending on which collider type acts as your legs, the weight of the objects involved, what behaviour you intend and more the solution can vary.
Possible solution 1:
One option is to freeze the rotation of your object on it's Rigidbody component, you can decide which axes to freeze rotation on. If freezed they can't be pushed by outside forces.
Possible solution 2:
If you wish the player to only be affected by your script you can set the rigidbody kinematic, meaning that it only is affected by scripts and animation. This is a contrast to being set to dynamic where other objects can affect it.
Possible solution 3:
Play around with the angular drag and mass of the rigidbody to slow down rotation.
I'm creating a Vive VR Passive VR experience, where your in a space ship and without any controls, it moves passively through the whole solar system. It's not AI, there will be a predetermined destination.
My question: How to make on object move passively?(A.K.A Space Ship with cameras)
You have a starting point, and a destination point, then Lerp between them. The examle in the unity documentation has a example for your exact question.
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour {
public Transform startMarker;
public Transform endMarker;
public float speed = 1.0F;
private float startTime;
private float journeyLength;
void Start() {
startTime = Time.time;
journeyLength = Vector3.Distance(startMarker.position, endMarker.position);
}
void Update() {
float distCovered = (Time.time - startTime) * speed;
float fracJourney = distCovered / journeyLength;
transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fracJourney);
}
}
You would attach that script to your "Spaceship" root object, you would then make the player a child of the spaceship so it will move with the ship as it goes along it's route.
Path Magic on the Asset Store could do it all for you, and you probably don't have to code anything.
Within unity 3D, Im building a 2d runner within which I have created a particle system that emits snow like particles from a single location at the top right corner of the main camera off screen.
I attached the particle emitter to the main camera so theres always snow like particles on screen, however, as the speed of the main camera increases, so does the speed of the particle emitter, which eventually starts emitting particles off screen.
How can I change the settings of the emitter so that the speed of the emitter does not affect the direction of the particles. Lastly for more details, I have set the shape of the emitter to be a cone, and the simulation space to be world. Any help would be much appreciated
You can change the Simulation Space setting of your ParticleSystem component : Particle System main module.
EDIT
Since I didn't noticed at first you wanted the particles speed to increase with the Camera accelerating (my bad...) here's a way you could fake the effect :
public class TestScript : MonoBehaviour
{
#region Attributes
[SerializeField]
private float m_MaxTransformSpeed;
[SerializeField]
private float m_MinStartSpeed;
[SerializeField]
private float m_MaxStartSpeed;
private ParticleSystem m_Particles;
private Vector3 m_PreviousPosition;
#endregion
#region MonoBehaviour
protected void Start()
{
m_Particles = GetComponent<ParticleSystem>();
}
protected void Update()
{
Vector3 cameraSpeed = transform.position - m_PreviousPosition;
m_Particles.startSpeed = Mathf.Clamp(m_MinStartSpeed + (m_MaxStartSpeed - m_MinStartSpeed) * cameraSpeed.magnitude / m_MaxTransformSpeed, m_MinStartSpeed, m_MaxStartSpeed);
m_PreviousPosition = transform.position;
}
#endregion
}
Instead of being based on only the latest frame you can either save N older positions and get the average speed value of N deltas or directly send the script the amount of movement you apply to your Camera if you move it manually.