How do I add a block placement/destruction feature?

Hello! I am making a 2D sandbox survival game (Think minecraft or terraria) and I am trying to implement a block placement/destruction system. I’ve been attempting to make this work, to no avail. Here is my character script.

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

[RequireComponent(typeof(Rigidbody2D))]
public class Oliver : MonoBehaviour {


    public float speed;

    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 idleSprite;
    public float jumpSpeed;

    bool isWalking;
    bool isGrounded;

    RaycastHit2D hit;

    bool facingRight;

    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));

        //ignore this
        if (r.velocity.magnitude > 0.1f && !isGrounded)
        {

            //GetComponent<SpriteRenderer>().sprite = walkSprite2;

        }

        //Does the walking and jumping animations
        if (Input.GetKeyDown(walkLeftKey))
        {
            StartCoroutine(Walk());
        } else if (Input.GetKeyDown(walkRightKey))
        {
            StartCoroutine(Walk());
        } else if (isGrounded)
        {
            if (Input.GetKeyDown(jumpKey))
            {
                r.velocity = new Vector2(r.velocity.x, jumpSpeed);

                StartCoroutine(Jump());

            }
        } else if (!isWalking)
        {
            GetComponent<SpriteRenderer>().sprite = idleSprite;
        }
        //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
       //This is the attempted block breaking system
      
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 c = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            RaycastHit2D hit2D = Physics2D.Raycast(transform.position, c - transform.position);

            if (hit2D.collider.gameObject != null)
            {
                Destroy(hit2D.collider.gameObject);
            }
        }
    }

    //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);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        GetComponent<SpriteRenderer>().sprite = walkSprite3;
        yield return new WaitForSeconds(0.5f);
        GetComponent<SpriteRenderer>().sprite = walkSprite7;
        yield return new WaitForSeconds(0.5f);
    }

}

For the mostpart it works but with the block destruction it does nothing. I am new to C# and Unity so maybe I made a beginner mistake.

Please check this thread and edit your post to use code tags: Using code tags properly
:slight_smile:

I’ve edited your code and put in some comments. I think the issue is the raycast. you’re not hitting what you think you’re hitting. so it’s always null

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

[RequireComponent(typeof(Rigidbody2D))]
public class Oliver : MonoBehaviour
{


    public float speed;

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

    //these sprite should be in a array, something for you to look into.
    //public Sprite[] walkSprites;
    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 idleSprite;
    public float jumpSpeed;

    bool isWalking;
    bool isGrounded;

    RaycastHit2D hit;

    bool facingRight;

    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)
        {
            GetComponent<SpriteRenderer>().flipX = false;
        }else if (Input.GetAxis("Horizontal") < 0)
        {
            GetComponent<SpriteRenderer>().flipX = true;
        }

        //End of flipping sprite

        //not being used
        //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());
        } else if (Input.GetKeyDown(walkRightKey))
        {
            StartCoroutine(Walk());
        } else if (isGrounded)
        {
            if (Input.GetKeyDown(jumpKey))
            {
                r.velocity = new Vector2(r.velocity.x, jumpSpeed);

                StartCoroutine(Jump());

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


        //This is the attempted block breaking system

        if (Input.GetMouseButtonDown(0))
        {
            Vector3 c = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            RaycastHit2D hit2D = Physics2D.Raycast(transform.position, c - transform.position);// i assume this is the issue. 
            //try this, it should tell you if the raycast is hitting where you think it should be hitting.
            Debug.DrawRay(transform.position, c - transform.position, Color.green);

            if (hit2D.collider.gameObject != null)
            {
                //Debug.Log("I hit : " + hit2D.name);
                Destroy(hit2D.collider.gameObject);
            }
        }
    }

    //Walk animation
    IEnumerator Walk()
    {
    //use the array here in a loop to shorten code
        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);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        GetComponent<SpriteRenderer>().sprite = walkSprite3;
        yield return new WaitForSeconds(0.5f);
        GetComponent<SpriteRenderer>().sprite = walkSprite7;
        yield return new WaitForSeconds(0.5f);
    }

}

i would also remove as many getcomponents<> that I could. They are bad for performance.

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

[RequireComponent(typeof(Rigidbody2D))]
public class Oliver : MonoBehaviour
{


    public float speed;

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

    //these sprite should be in a array, something for you to look into.
    //public Strite[] walkSprites;
    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 idleSprite;
    public float jumpSpeed;

    bool isWalking;
    bool isGrounded;

    RaycastHit2D hit;

    bool facingRight;
   
    private SpriteRenderer spriteRenderer;
    private Rigidbody2D r;
   
    void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        r = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        //flips the sprite
        r.velocity = new Vector2(Input.GetAxis("Horizontal") * speed, r.velocity.y);

        if (Input.GetAxis ("Horizontal") > 0)
        {
            spriteRenderer.flipX = false;
        }else if (Input.GetAxis("Horizontal") < 0)
        {
            spriteRenderer.flipX = true;
        }

        //End of flipping sprite

        //not being used
        //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());
        } else if (Input.GetKeyDown(walkRightKey))
        {
            StartCoroutine(Walk());
        } else if (isGrounded)
        {
            if (Input.GetKeyDown(jumpKey))
            {
                r.velocity = new Vector2(r.velocity.x, jumpSpeed);

                StartCoroutine(Jump());

            }
        } else if (!isWalking)
        {
            spriteRenderer.sprite = idleSprite;
        }
        //End of animation runner


        //This is the attempted block breaking system

        if (Input.GetMouseButtonDown(0))
        {
            Vector3 c = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            RaycastHit2D hit2D = Physics2D.Raycast(transform.position, c - transform.position);// i assume this is the issue. 
            //try this, it should tell you if the raycast is hitting where you think it should be hitting.
            Debug.DrawRay(transform.position, c - transform.position, Color.green);

            if (hit2D.collider.gameObject != null)
            {
                //Debug.Log("I hit : " + hit2D.name);
                Destroy(hit2D.collider.gameObject);
            }
        }
    }

    //Walk animation
    IEnumerator Walk()
    {
    //use the array here in a loop to shorten code
        isWalking = true;
        spriteRenderer.sprite = walkSprite1;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite2;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite3;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite4;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite5;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite6;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite7;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite8;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite9;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite10;
        yield return new WaitForSeconds(0.05f);
        spriteRenderer.sprite = walkSprite11;
        yield return new WaitForSeconds(0.05f);
        isWalking = false;
    }

    //Jump animation
    IEnumerator Jump()
    {
        spriteRenderer.sprite = walkSprite3;
        yield return new WaitForSeconds(0.5f);
        spriteRenderer.sprite = walkSprite7;
        yield return new WaitForSeconds(0.5f);
    }

}

Made 2 corrections, 1 to prevent an exception & one to solve your raycast hit issue, I believe :slight_smile:

if (Input.GetMouseButtonDown(0))
{
    /* Changed */
    Vector3 c = Camera.main.ScreenToWorldPoint(new Vector3(
    Input.mousePosition.x, Input.mousePosition.y,
    Mathf.Abs(Camera.main.transform.position.z - transform.position.z)));

    RaycastHit2D hit2D = Physics2D.Raycast(transform.position, c - transform.position);// i assume this is the issue.
   
    //try this, it should tell you if the raycast is hitting where you think it should be hitting.
    Debug.DrawRay(transform.position, c - transform.position, Color.green,1);

    /* Changed - removed '.gameObject', because if '.collider' is null, it would throw an exception */
    if (hit2D.collider != null)
    {
        Destroy(hit2D.collider.gameObject);
    }
 }

Thank you for the help! I’m new to C# and Unity. But… I keep getting this error:
NullReferenceException: Object reference not set to an instance of an object
Oliver.Update () (at Assets/Scripts/Character Scripts/Oliver.cs:116)
I’m using a 2D Box collider, is that a problem?

Something is null. Post the line in question?

On your own, you could check each possible variable that could be null, and make sure it’s not :slight_smile:

if (!hit2D)
{
Destroy(hit2D.collider.gameObject);
}

If that’s the line, I think you want !hit2D.collider (like in my post)

It… was the camera. Now it deletes the camera, but I think I can fix that… probably. Yup! Got it!

Ah okay. Glad ya got it working/fixed. :slight_smile: