Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Controlling specific particle force / custom constraints.
#10
Here's my version of it using ropes (instead of rods). Haven't played Snake Pass though, only seen videos, so I'm not sure if this mimics the gameplay feel close enough. It's kinda tricky to move around as a snake but I guess that's the challenge Sonrisa

You can move the head using WASD, lift up the head using Space, and slither (accelerate) pressing J:



Here's the full snake controller code:

Code:
using UnityEngine;
using Obi;

public class SnakeController : MonoBehaviour
{
    public Transform headReferenceFrame;
    public float headSpeed = 20;
    public float upSpeed = 40;
    public float slitherSpeed = 10;

    private ObiRope rope;
    private ObiSolver solver;
    private float[] traction;
    private Vector3[] surfaceNormal;

    private void Start()
    {
        rope = GetComponent<ObiRope>();
        solver = rope.solver;

        // initialize traction array:
        traction = new float[rope.activeParticleCount];
        surfaceNormal = new Vector3[rope.activeParticleCount];

        // subscribe to solver events (to update surface information)
        solver.OnBeginStep += ResetSurfaceInfo;
        solver.OnCollision += AnalyzeContacts;
        solver.OnParticleCollision += AnalyzeContacts;
    }


    private void OnDestroy()
    {
        solver.OnBeginStep -= ResetSurfaceInfo;
        solver.OnCollision -= AnalyzeContacts;
        solver.OnParticleCollision -= AnalyzeContacts;
    }

    private void ResetSurfaceInfo(ObiSolver s, float stepTime)
    {
        // reset surface info:
        for (int i = 0; i < traction.Length; ++i)
        {
            traction[i] = 0;
            surfaceNormal[i] = Vector3.zero;
        }
    }

    private void AnalyzeContacts(object sender, ObiSolver.ObiCollisionEventArgs e)
    {
        // iterate trough all contacts:
        for (int i = 0; i < e.contacts.Count; ++i)
        {
            var contact = e.contacts.Data[i];
            if (contact.distance < 0.005f)
            {
                int simplexIndex = solver.simplices[contact.bodyA];
                var particleInActor = solver.particleToActor[simplexIndex];

                // using 1 here, could calculate a traction value based on the type of terrain, friction, etc.
                traction[particleInActor.indexInActor] = 1;

                // accumulate surface normal:
                surfaceNormal[particleInActor.indexInActor] += (Vector3)contact.normal;
            }
        }
    }

    public void Update()
    {
        if (Input.GetKey(KeyCode.J))
        {
            for (int i = 1; i < rope.activeParticleCount; ++i)
            {
                int solverIndex = rope.solverIndices[i];
                int prevSolverIndex = rope.solverIndices[i - 1];

                // direction from current particle to previous one, projected on the contact surface:
                Vector4 dir = Vector3.ProjectOnPlane(solver.positions[prevSolverIndex] - solver.positions[solverIndex], surfaceNormal[i]).normalized;

                // move in that direction:
                solver.velocities[solverIndex] += dir * traction[i] * slitherSpeed * Time.deltaTime;
            }
        }

        int headIndex = rope.solverIndices[0];

        if (headReferenceFrame != null)
        {
            Vector3 direction = Vector3.zero;

            // Determine movement direction:
            if (Input.GetKey(KeyCode.W))
            {
                direction += headReferenceFrame.forward * headSpeed;
            }
            if (Input.GetKey(KeyCode.A))
            {
                direction += -headReferenceFrame.right * headSpeed;
            }
            if (Input.GetKey(KeyCode.S))
            {
                direction += -headReferenceFrame.forward * headSpeed;
            }
            if (Input.GetKey(KeyCode.D))
            {
                direction += headReferenceFrame.right * headSpeed;
            }

            // flatten out the direction so that it's parallel to the ground:
            direction.y = 0;

            solver.velocities[headIndex] += (Vector4)direction * Time.deltaTime;
        }

        if (Input.GetKey(KeyCode.Space))
            solver.velocities[headIndex] += (Vector4)Vector3.up * Time.deltaTime * upSpeed;
    }
}

This is the interesting bit:

Code:
if (Input.GetKey(KeyCode.J))
{
        for (int i = 1; i < rope.activeParticleCount; ++i)
        {
                int solverIndex = rope.solverIndices[i];
                int prevSolverIndex = rope.solverIndices[i - 1];

                // direction from current particle to previous one, projected on the contact surface:
                Vector4 dir = Vector3.ProjectOnPlane(solver.positions[prevSolverIndex] - solver.positions[solverIndex], surfaceNormal[i]).normalized;

                // move in that direction:
                solver.velocities[solverIndex] += dir * traction[i] * slitherSpeed * Time.deltaTime;
        }
}

By moving each particle in the direction of the previous one in the snake's body, the typical slithery-snake movement is achieved. That's all there is to it.

Only particles that are in contact with something can slither, though. This is achieved by storing a "traction" value for each particle. Particles in contact with something have a traction of 1, if they're not in contact with anything they have zero traction. If you wanted to I guess you could have different traction values for different terrains and stuff.

This will be included as a sample scene in upcoming updates.

cheers!
Reply


Messages In This Thread
RE: Controlling specific particle force / custom constraints. - by josemendez - 23-04-2021, 11:09 AM