2D Swimming and water mechanics

Hello! I am creating a game where you are on a raft and have to manage your health, hunger, thirst, and exposure as well as other things while looking for an island to settle on. Currently, I am trying to implement a water system. Basically, I already have a system for walking and jumping, but I want to add swimming when in the water, as well as objects floating if possible. I’d also like to add breath. I am a beginner, and I’ve done research but what I’ve found is too outdated or wasn’t what I was looking for. I am using pixels for everything as well. If it will help, this is the script for my character. If you decide to help it’s very much appreciated, thanks ^^.

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {

    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode walkRightKey = KeyCode.D;
    public KeyCode walkLeftKey = KeyCode.A;

    public Sprite walkSprite1;
    public Sprite walkSprite2;
    public Sprite walkSprite3;
    public Sprite walkSprite4;
    public Sprite walkSprite5;
    public Sprite walkSprite6;
    public Sprite walkSprite7;
    public Sprite walkSprite8;
    public Sprite walkSprite9;
    public Sprite walkSprite10;
    public Sprite walkSprite11;
    public Sprite walkSprite12;
    public Sprite walkSprite13;
    public Sprite walkSprite14;

    public Sprite idleSprite1;
   
    public Sprite jumpSprite1;
    public Sprite jumpSprite2;
    public Sprite jumpSprite3;
    public Sprite jumpSprite4;
    public Sprite jumpSprite5;
    public Sprite jumpSprite6;

    public float speed;
    public float jumpSpeed;

    bool isWalking;
    bool isGrounded;
    bool facingRight;

    RaycastHit2D hit;

    void Start () {
       
    }
   
   
    void Update () {

        //flips the sprite
        Rigidbody2D r = GetComponent<Rigidbody2D>();
        r.velocity = new Vector2(Input.GetAxis("Horizontal") * speed, r.velocity.y);

        if (Input.GetAxis("Horizontal") > 0)
        {
            facingRight = true;

        }

        if (Input.GetAxis("Horizontal") < 0)
        {
            facingRight = false;

        }

        if (!facingRight)
        {
            GetComponent<SpriteRenderer>().flipX = true;
        }
        else
        {
            GetComponent<SpriteRenderer>().flipX = false;
        }
        //End of flipping sprite


        hit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y), Vector2.down);
        Debug.Log(Vector2.Distance(new Vector2(transform.position.x, transform.position.y), hit.collider.transform.position));

        //Does the walking and jumping animations
        if (Input.GetKeyDown(walkLeftKey))
        {
            StartCoroutine(Walk());
            isWalking = true;

        }
        else if (Input.GetKeyDown(walkRightKey))
        {

            StartCoroutine(Walk());
            isWalking = true;


        }
        else if (Input.GetKeyDown(jumpKey))
        {
            r.velocity = new Vector2(r.velocity.x, jumpSpeed);

            StartCoroutine(Jump());
            isWalking = true;
        }

        if (!isWalking)
        {
            GetComponent<SpriteRenderer>().sprite = idleSprite1;
        }
        //End of animation runner

        //Just for debugging
        if (hit.distance < 1.9f)
        {
            isGrounded = true;
            Debug.Log("On ground.");
        }
        else
        {
            isGrounded = false;
            Debug.Log("Off ground.");
        }
        //End of debugging
    }

    //Walk animation
    IEnumerator Walk()
    {
        isWalking = true;
        GetComponent<SpriteRenderer>().sprite = walkSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite6;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite7;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite8;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite9;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite10;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite11;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite12;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite13;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite14;
        yield return new WaitForSeconds(0.05f);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        GetComponent<SpriteRenderer>().sprite = jumpSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite6;
        yield return new WaitForSeconds(0.05f);
    }
} [CODE]

I can offer a couple of pretty helpful tips for your code, though unrelated to your question.

  1. Cache the sprite render in Awake() and use the variable throughout your code, instead of calling GetComponent all of the time.

  2. Use arrays for walking and jumping sprites, instead of each one being its own variable. Use the index in the array when you need them.

As for your actual question: What are you looking for exactly? Floating, like in the water, they might bob a little but otherwise not ‘fall’ (ie: not affected by gravity).
Swimming… Is that to say you just want a different animation while moving in the water or something else?

Mark water zones with giant triggers areas that change the object’s behaviour, especially in relation to gravity.

private void OnTriggerEnter(Collider other)
{
    if(other.gameObject.CompareTag("Water Zone") == true)
    {
         controller.isSwimming = true;
    }
}

You already have isWalking, so it should slide right in to your idea.

Yes on the floating, but for the swimming I mean not as effected by gravity, and able to move up and down as well. And yes, also different animations.

So, if you build on @GroZZleR 's response, you can act accordingly (eg: isSwimming or not). That’s for movement.

The same general idea could be applied to floating, minus the movement :wink:

How would I make it so the water is a trigger yet doesn’t a collider that stops the player? This is what I have for the swimming movement so far.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {

    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode walkRightKey = KeyCode.D;
    public KeyCode walkLeftKey = KeyCode.A;
    public KeyCode walkUpKey = KeyCode.W;
    public KeyCode walkDownKey = KeyCode.S;

    public Sprite walkSprite1;
    public Sprite walkSprite2;
    public Sprite walkSprite3;
    public Sprite walkSprite4;
    public Sprite walkSprite5;
    public Sprite walkSprite6;
    public Sprite walkSprite7;
    public Sprite walkSprite8;
    public Sprite walkSprite9;
    public Sprite walkSprite10;
    public Sprite walkSprite11;
    public Sprite walkSprite12;
    public Sprite walkSprite13;
    public Sprite walkSprite14;

    public Sprite idleSprite1;
   
    public Sprite jumpSprite1;
    public Sprite jumpSprite2;
    public Sprite jumpSprite3;
    public Sprite jumpSprite4;
    public Sprite jumpSprite5;
    public Sprite jumpSprite6;

    public float speed;
    public float jumpSpeed;
    public float swimUpSpeed;
    public float swimDownSpeed;

    bool isWalking;
    bool isGrounded;
    bool facingRight;
    bool isSwimming;

    RaycastHit2D hit;

    void Start () {
       
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Water") == true)
        {
            bool isSwimming = true;
            float jumpSpeed = 5;
        }
    }


    void Update () {



        //flips the sprite
        Rigidbody2D r = GetComponent<Rigidbody2D>();
        r.velocity = new Vector2(Input.GetAxis("Horizontal") * speed, r.velocity.y);

        if (Input.GetAxis("Horizontal") > 0)
        {
            facingRight = true;

        }

        if (Input.GetAxis("Horizontal") < 0)
        {
            facingRight = false;

        }

        if (!facingRight)
        {
            GetComponent<SpriteRenderer>().flipX = true;
        }
        else
        {
            GetComponent<SpriteRenderer>().flipX = false;
        }
        //End of flipping sprite


        hit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y), Vector2.down);
        Debug.Log(Vector2.Distance(new Vector2(transform.position.x, transform.position.y), hit.collider.transform.position));

        //Does the walking and jumping animations
        if (Input.GetKeyDown(walkLeftKey))
        {
            StartCoroutine(Walk());
            isWalking = true;

        }
        else if (Input.GetKeyDown(walkRightKey))
        {

            StartCoroutine(Walk());
            isWalking = true;


        }
        /*else if (Input.GetKeyDown(walkUpKey))
        {
            if (isSwimming == true)
            {
                r.velocity = new Vector2(r.velocity.x, swimUpSpeed);
                StartCoroutine(Walk());
            }


        }
        else if (Input.GetKeyDown(walkDownKey))
        {
            if (isSwimming == true) {
                r.velocity = new Vector2(r.velocity.x, swimDownSpeed);
                StartCoroutine(Walk());

        }


        }*/
        else if (Input.GetKeyDown(jumpKey))
        {
            r.velocity = new Vector2(r.velocity.x, jumpSpeed);

            StartCoroutine(Jump());
            isWalking = true;
        }

        if (!isWalking)
        {
            GetComponent<SpriteRenderer>().sprite = idleSprite1;
        }
        //End of animation runner

        //Just for debugging
        if (hit.distance < 1.9f)
        {
            isGrounded = true;
            Debug.Log("On ground.");
        }
        else
        {
            isGrounded = false;
            Debug.Log("Off ground.");
        }
        //End of debugging
    }

    //Walk animation
    IEnumerator Walk()
    {
        isWalking = true;
        GetComponent<SpriteRenderer>().sprite = walkSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite6;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite7;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite8;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite9;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite10;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite11;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite12;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite13;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite14;
        yield return new WaitForSeconds(0.05f);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        GetComponent<SpriteRenderer>().sprite = jumpSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite6;
        yield return new WaitForSeconds(0.05f);
    }
}

[CODE]

If your question was “How do I make it a trigger without colliding with the player” the answer is that triggers don’t act as collisions.

If it was something else, could you please explain? :slight_smile: Sometimes, I am slow… heh

Oh, and make sure on the collider you check the little box titled “IsTrigger”.

This is the current code I have, and I’ve noticed that it seems that when the player passes through the “water” (it has a box collider with isTrigger checked) nothing happens. Am I doing something wrong? Sorry, I’m new to this.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {



    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode walkRightKey = KeyCode.D;
    public KeyCode walkLeftKey = KeyCode.A;
    public KeyCode walkUpKey = KeyCode.W;
    public KeyCode walkDownKey = KeyCode.S;

    public float speed = 6.0F;
    public float jumpSpeed = 8.0F;
    public float gravity = 20.0F;
    private Vector3 moveDirection = Vector3.zero;

    public Sprite walkSprite1;
    public Sprite walkSprite2;
    public Sprite walkSprite3;
    public Sprite walkSprite4;
    public Sprite walkSprite5;
    public Sprite walkSprite6;
    public Sprite walkSprite7;
    public Sprite walkSprite8;
    public Sprite walkSprite9;
    public Sprite walkSprite10;
    public Sprite walkSprite11;
    public Sprite walkSprite12;
    public Sprite walkSprite13;
    public Sprite walkSprite14;

    public Sprite idleSprite1;
   
    public Sprite jumpSprite1;
    public Sprite jumpSprite2;
    public Sprite jumpSprite3;
    public Sprite jumpSprite4;
    public Sprite jumpSprite5;
    public Sprite jumpSprite6;

    //public float speed;
    private bool conroller;
    //public float jumpSpeed;
    public float swimUpSpeed;
    public float swimDownSpeed;

    bool isWalking;
    bool isGrounded;
    bool facingRight;
    bool isSwimming;

    RaycastHit2D hit;

    void Start () {
       
    }

   

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Water") == true)
        {
            isSwimming = true;
            gravity= 5;
            Debug.Log("In Water");
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (other.gameObject.CompareTag("Water") == true)
        {
            isSwimming = true;
            gravity = -10;
        }
    }


    void Update () {

        CharacterController controller = GetComponent<CharacterController>();

        //flips the sprite
        Rigidbody2D r = GetComponent<Rigidbody2D>();
        r.velocity = new Vector2(Input.GetAxis("Horizontal") * speed, r.velocity.y);

        if (Input.GetAxis("Horizontal") > 0)
        {
            facingRight = true;

        }

        if (Input.GetAxis("Horizontal") < 0)
        {
            facingRight = false;

        }

        if (!facingRight)
        {
            GetComponent<SpriteRenderer>().flipX = true;
        }
        else
        {
            GetComponent<SpriteRenderer>().flipX = false;
        }
        //End of flipping sprite


        hit = Physics2D.Raycast(new Vector2(transform.position.x, transform.position.y), Vector2.down);
        Debug.Log(Vector2.Distance(new Vector2(transform.position.x, transform.position.y), hit.collider.transform.position));

        //Does the walking and jumping animations
        if (Input.GetKeyDown(walkLeftKey))
        {
            StartCoroutine(Walk());
            isWalking = true;

        }
        else if (Input.GetKeyDown(walkRightKey))
        {

            StartCoroutine(Walk());
            isWalking = true;


        }
        else if (Input.GetKeyDown(walkUpKey))
        {
            if (isSwimming == true)
            {
                r.velocity = new Vector2(r.velocity.x, swimUpSpeed);
                StartCoroutine(Walk());
            }


        }
        else if (Input.GetKeyDown(walkDownKey))
        {
            if (isSwimming == true) {
                r.velocity = new Vector2(r.velocity.x, swimDownSpeed);
                StartCoroutine(Walk());

        }


        }
        else if (Input.GetKeyDown(jumpKey))
        {
            r.velocity = new Vector2(r.velocity.x, jumpSpeed);

            StartCoroutine(Jump());
            isWalking = true;
        }

        if (!isWalking)
        {
            GetComponent<SpriteRenderer>().sprite = idleSprite1;
        }
        //End of animation runner
       

       
        // is the controller on the ground?
        if (controller.isGrounded)
        {
            //Feed moveDirection with input.
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
            moveDirection = transform.TransformDirection(moveDirection);
            //Multiply it by speed.
            moveDirection *= speed;
            //Jumping
            if (Input.GetButton("jumpKey"))
                moveDirection.y *= jumpSpeed;



        }
        //Applying gravity to the controller
        moveDirection.y -= gravity * Time.deltaTime;
        //Making the character move
        controller.Move(moveDirection * Time.deltaTime);
   
       

        //Just for debugging
        if (hit.distance < 1.9f)
        {
            isGrounded = true;
            Debug.Log("On ground.");
        }
        else
        {
            isGrounded = false;
            Debug.Log("Off ground.");
        }
        //End of debugging
    }

    //Walk animation
    IEnumerator Walk()
    {
        isWalking = true;
        GetComponent<SpriteRenderer>().sprite = walkSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite6;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite7;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite8;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite9;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite10;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite11;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite12;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite13;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = walkSprite14;
        yield return new WaitForSeconds(0.05f);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        GetComponent<SpriteRenderer>().sprite = jumpSprite1;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite2;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite3;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite4;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite5;
        yield return new WaitForSeconds(0.05f);
        GetComponent<SpriteRenderer>().sprite = jumpSprite6;
        yield return new WaitForSeconds(0.05f);
    }
}

[CODE]

Do you see the “In Water” debug line in the console window? If not, you probably haven’t tagged the water zone properly.

In addition to checking that the debug is working, you have set “isSwimming” to true in both enter and exit statements. Also, I believe you have gravity backwards (though I’m not sure if you just want it off while swimming to begin with).

I made sure it was tagged correctly, yet still nothing happens. And I fixed the on ground debugging. I’m using a rigidbody 2d and box collider for the player, and a box collider for the water.

Nothing, not even the debug log?

Your character has a rigidbody on it, correct?

Yeah. I probably am not doing something obvious, since I’m new, but I’m pretty sure I have everything tagged and whatnot.

If you’re still stuck after my last response, if you can create a small unity package and post it in this thread, I can take a look at it for you, if you’d like.

Try putting Debug.Log("In Trigger"); just above your if statement in OnTriggerEnter. It should look like this:

    void OnTriggerEnter(Collider other)
    {
        Debug.Log("In Trigger");
        if (other.gameObject.CompareTag("Water") == true)
        {
            isSwimming = true;
            gravity= 5;
            Debug.Log("In Water");
        }
    }

If you start seeing “In Trigger” but not “In Water”, double check the tag on your water object. Remember that capitalization matters, that is to say, it won’t work if it’s tagged as “water” instead of “Water”. I can’t tell you how many times I’ve seen people (including myself) forget that.

Will this work?

3378641–265108–Land-Ho.zip (10.7 KB)

No, unfortunately. Unity scenes don’t contain everything, they need the assets used in them to work. The easiest two ways to send your projects are either to zip up the whole project folder, or, much simple, you can right click on your scene file within unity, and choose “Export Package”. This will make a package file that someone else can import into their project. That’s the easiest way for you to show us your project.

Oh! Sorry!

3378666–265110–Land-Ho.unitypackage (80.6 KB)

Checking it out now.

Okay, so you had a few issues I quickly fixed up & 1 “oops” oversight from this thread lol :slight_smile:

So, you were trying OnTriggerEnter and OnTriggerExit and I/we should have corrected you that you should be using OnTriggerEnter2D and OnTriggerExit2D

The errors I fixed were related to index out of range. You are writing you code as though indexes begin at ‘1’, but they begin at ‘0’. :slight_smile:

As for the actual water floating implementation, I have not worked that out for you. However, if you make these corrections, perhaps you can at least be on track to tackle that next issue. Ok? :slight_smile: