glutton free

Role: Programmer, Game Designer


Tool: Unity, Photoshop


Team: Ludum Dare 54 (Limited Space), Team of 4


Timeline: 72 hours


Completion Date: October 2, 2023

Role: Programmer, Game Designer


Tool: Unity, Photoshop


Team: Ludum Dare 54 (Limited Space), Team of 4


Timeline: 72 hours


Completion Date: October 2, 2023

Role: Programmer, Game Designer


Tool: Unity, Photoshop


Team: Ludum Dare 54 (Limited Space), Team of 4


Timeline: 72 hours


Completion Date: October 2, 2023

Play ->

Play ->

Play ->

GAME DESIGN

GAME DESIGN

GAME DESIGN

design pillars

design pillars

design pillars

Physics-Based Gameplay

Physics-Based Gameplay

Physics-Based Gameplay

Build around tactile, satisfying movement and collisions integral to the core loop.

Build around tactile, satisfying movement and collisions integral to the core loop.

Build around tactile, satisfying movement and collisions integral to the core loop.

Accessible Depth

Accessible Depth

Accessible Depth

Create simple controls with mastery-driven mechanics for casual and competitive appeal.

Create simple controls with mastery-driven mechanics for casual and competitive appeal.

Create simple controls with mastery-driven mechanics for casual and competitive appeal.

Replayable Variety

Replayable Variety

Replayable Variety

Offer diverse scenarios and clear paths for skill improvement, ensuring unique and rewarding replays.

Offer diverse scenarios and clear paths for skill improvement, ensuring unique and rewarding replays.

Offer diverse scenarios and clear paths for skill improvement, ensuring unique and rewarding replays.

mechanics

mechanics

mechanics

PROGRAMMING

PROGRAMMING

PROGRAMMING

soft body physics

soft body physics

soft body physics

The game’s uniqueness hinges on 2D soft body physics, achieved by simulating collisions and interactions between multiple rigid bodies within the player’s character. A physical cursor allows direct interaction with the character, while a boost mechanic lets players occasionally launch their character explosively in a chosen direction to enhance survivability.

The game’s uniqueness hinges on 2D soft body physics, achieved by simulating collisions and interactions between multiple rigid bodies within the player’s character. A physical cursor allows direct interaction with the character, while a boost mechanic lets players occasionally launch their character explosively in a chosen direction to enhance survivability.

The game’s uniqueness hinges on 2D soft body physics, achieved by simulating collisions and interactions between multiple rigid bodies within the player’s character. A physical cursor allows direct interaction with the character, while a boost mechanic lets players occasionally launch their character explosively in a chosen direction to enhance survivability.

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

public class Blob : MonoBehaviour
{
    private class PropagateCollisions : MonoBehaviour
    {
        GameObject zPlayer;

        private void Start()
        {
            zPlayer = GameObject.FindGameObjectWithTag("PlayerMain");
        }

        void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.transform.tag == "Sticky")
            {
                gameObject.GetComponent<Rigidbody2D>().constraints =
                    RigidbodyConstraints2D.FreezePositionY
                    | RigidbodyConstraints2D.FreezePositionX
                    | RigidbodyConstraints2D.FreezeRotation;
            }
        }

        private void OnTriggerEnter2D(Collider2D collision)
        {
            if (collision.transform.tag == "DetachOne")
            {
                gameObject.GetComponent<Rigidbody2D>().constraints = RigidbodyConstraints2D.None;
            }
            if (collision.transform.tag == "DetachAll")
            {
                zPlayer.GetComponent<Blob>().TrigThis();
            }
            if (collision.transform.tag == "Virus")
            {
                Blob blob = zPlayer.GetComponent<Blob>();
                Destroy(collision.gameObject);
                blob.Grow();
                StatsManager.Instance.IncVirusCount();
            }

            if (collision.transform.tag == "Medicine")
            {
                Blob blob = zPlayer.GetComponent<Blob>();
                Destroy(collision.gameObject);
                // audioSource.PlayOneShot(eatProbiotic);
                blob.Shrink(false);
                StatsManager.Instance.IncVirusCount();
            }
        }
    }

    public int width = 5;
    public int height = 5;
    public int referencePointsCount = 12;
    public float referencePointRadius = 0.25f;
    public float mappingDetail = 10;
    public float springDampingRatio = 0;
    public float springFrequency = 2;
    public float scaleGrowth = 1.05f;
    public float fartForce = 250;
    public float fartCooldown = 5;
    public PhysicsMaterial2D surfaceMaterial;
    public Rigidbody2D[] allReferencePoints;
    public Image coolDown;
    public GameObject fartPrefab;
    public AudioSource audioSource;
    public AudioClip fartSound;
    public AudioClip eatFood;
    public AudioClip eatProbiotic;
    public Material blobbyMat;
    public List<Texture> blobbyTextures;
    public GameObject[] referencePoints { private set; get; }
    public GameObject centerPoint { private set; get; }
    int vertexCount;
    Vector3[] vertices;
    int[] triangles;
    Vector2[] uv;
    Vector3[,] offsets;
    float[,] weights;
    float lastFartTime;

    void Start()
    {
        CreateReferencePoints();
        CreateCenterPoint();
        IgnoreCollisionsBetweenReferencePoints();
        CreateMesh();
        MapVerticesToReferencePoints();

        ChangeScale(-2.5f);
        lastFartTime = -fartCooldown;
        blobbyMat.mainTexture = blobbyTextures[0];
        GameManager.Instance.state = GameState.IN_GAME;
    }

    void CreateReferencePoints()
    {
        Rigidbody2D rigidbody = GetComponent<Rigidbody2D>();
        referencePoints = new GameObject[referencePointsCount];
        Vector3 offsetFromCenter = (0.5f - referencePointRadius) * Vector3.up;
        float angle = 360.0f / referencePointsCount;

        for (int i = 0; i < referencePointsCount; i++)
        {
            referencePoints[i] = new GameObject { tag = gameObject.tag };
            referencePoints[i].AddComponent<PropagateCollisions>();
            referencePoints[i].transform.parent = transform;

            Quaternion rotation = Quaternion.AngleAxis(angle * (i - 1), Vector3.back);
            referencePoints[i].transform.localPosition = rotation * offsetFromCenter;

            Rigidbody2D body = referencePoints[i].AddComponent<Rigidbody2D>();
            body.constraints = RigidbodyConstraints2D.None;
            body.mass = 0.5f;
            body.interpolation = rigidbody.interpolation;
            body.collisionDetectionMode = rigidbody.collisionDetectionMode;
            body.drag = 0.5f;
            body.angularDrag = 0.5f;
            body.gravityScale = 0;
            allReferencePoints[i] = body;

            CircleCollider2D collider = referencePoints[i].AddComponent<CircleCollider2D>();
            collider.radius = referencePointRadius * (transform.localScale.x / 2);
            if (surfaceMaterial != null)
            {
                collider.sharedMaterial = surfaceMaterial;
            }

            AttachWithSpringJoint(referencePoints[i], gameObject, false);
            if (i > 0)
            {
                AttachWithSpringJoint(referencePoints[i], referencePoints[i - 1], false);
            }
        }
        AttachWithSpringJoint(referencePoints[0], referencePoints[referencePointsCount - 1], false);
    }

    void CreateCenterPoint()
    {
        // Create center point
        Rigidbody2D rigidbody = GetComponent<Rigidbody2D>();

        centerPoint = new GameObject();
        centerPoint.tag = gameObject.tag;
        centerPoint.AddComponent<PropagateCollisions>();
        centerPoint.transform.parent = transform;

        centerPoint.transform.localPosition = Vector3.zero;
        Rigidbody2D centerBody = centerPoint.AddComponent<Rigidbody2D>();
        centerBody.constraints = RigidbodyConstraints2D.None;
        centerBody.mass = 0.5f;
        centerBody.interpolation = rigidbody.interpolation;
        centerBody.collisionDetectionMode = rigidbody.collisionDetectionMode;
        centerBody.gravityScale = 0;

        CircleCollider2D centerCollider = centerPoint.AddComponent<CircleCollider2D>();
        centerCollider.radius = 3;
        if (surfaceMaterial != null)
        {
            centerCollider.sharedMaterial = surfaceMaterial;
        }

        for (int i = 0; i < referencePointsCount; i++)
        {
            AttachWithSpringJoint(centerPoint, referencePoints[i], true);
        }
    }

    void AttachWithSpringJoint(GameObject referencePoint, GameObject connected, bool isCenter)
    {
        SpringJoint2D springJoint = referencePoint.AddComponent<SpringJoint2D>();
        springJoint.connectedBody = connected.GetComponent<Rigidbody2D>();
        springJoint.connectedAnchor = LocalPosition(referencePoint) - LocalPosition(connected);
        springJoint.distance = isCenter ? width / 2 : 0;
        springJoint.dampingRatio = springDampingRatio;
        springJoint.frequency = springFrequency;
        springJoint.autoConfigureDistance = false;
    }

    void IgnoreCollisionsBetweenReferencePoints()
    {
        int i;
        int j;
        CircleCollider2D a;
        CircleCollider2D b;
        CircleCollider2D c = null;
        if (centerPoint != null)
            c = centerPoint.GetComponent<CircleCollider2D>();

        for (i = 0; i < referencePointsCount; i++)
        {
            for (j = i; j < referencePointsCount; j++)
            {
                a = referencePoints[i].GetComponent<CircleCollider2D>();
                b = referencePoints[j].GetComponent<CircleCollider2D>();
                Physics2D.IgnoreCollision(a, b, true);
            }
            if (c != null)
            {
                a = referencePoints[i].GetComponent<CircleCollider2D>();
                Physics2D.IgnoreCollision(a, c, true);
            }
        }
    }

    void CreateMesh()
    {
        vertexCount = (width + 1) * (height + 1);

        int trianglesCount = width * height * 6;
        vertices = new Vector3[vertexCount];
        triangles = new int[trianglesCount];
        uv = new Vector2[vertexCount];
        int t;

        for (int y = 0; y <= height; y++)
        {
            for (int x = 0; x <= width; x++)
            {
                int v = (width + 1) * y + x;
                vertices[v] = new Vector3(x / (float)width - 0.5f, y / (float)height - 0.5f, 0);
                uv[v] = new Vector2(x / (float)width, y / (float)height);

                if (x < width && y < height)
                {
                    t = 3 * (2 * width * y + 2 * x);

                    triangles[t] = v;
                    triangles[++t] = v + width + 1;
                    triangles[++t] = v + width + 2;
                    triangles[++t] = v;
                    triangles[++t] = v + width + 2;
                    triangles[++t] = v + 1;
                }
            }
        }

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.vertices = vertices;
        mesh.uv = uv;
        mesh.triangles = triangles;
    }

    void MapVerticesToReferencePoints()
    {
        offsets = new Vector3[vertexCount, referencePointsCount];
        weights = new float[vertexCount, referencePointsCount];

        for (int i = 0; i < vertexCount; i++)
        {
            float totalWeight = 0;

            for (int j = 0; j < referencePointsCount; j++)
            {
                offsets[i, j] = vertices[i] - LocalPosition(referencePoints[j]);
                weights[i, j] = 1 / Mathf.Pow(offsets[i, j].magnitude, mappingDetail);
                totalWeight += weights[i, j];
            }

            for (int j = 0; j < referencePointsCount; j++)
            {
                weights[i, j] /= totalWeight;
            }
        }
    }

    void Update()
    {
        UpdateVertexPositions();
        CheckFart();
        CheckSize();
    }

    private void CheckSize()
    {
        if (gameObject.transform.localScale.x >= StatsManager.maxSize)
        {
            GameManager.Instance.GoLost();
        }
    }

    void UpdateVertexPositions()
    {
        Vector3[] vertices = new Vector3[vertexCount];

        for (int i = 0; i < vertexCount; i++)
        {
            vertices[i] = Vector3.zero;

            for (int j = 0; j < referencePointsCount; j++)
            {
                vertices[i] += weights[i, j] * (LocalPosition(referencePoints[j]) + offsets[i, j]);
            }
        }

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.vertices = vertices;
        mesh.RecalculateBounds();
    }

    Vector3 LocalPosition(GameObject obj)
    {
        return transform.InverseTransformPoint(obj.transform.position);
    }

    public void TrigThis()
    {
        int z = 0;
        foreach (Rigidbody2D child in allReferencePoints)
        {
            allReferencePoints[z].constraints = RigidbodyConstraints2D.None;
            z++;
        }
    }

    private void CheckFart()
    {
        if (Input.GetMouseButtonDown(0) && Time.time > lastFartTime + fartCooldown && GameManager.Instance.GetGameStates() == GameState.IN_GAME)
        {
            Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            mousePos.z = transform.position.z;

            Vector2 moveDir = -(mousePos - transform.position).normalized;
            centerPoint.GetComponent<Rigidbody2D>().velocity += moveDir * fartForce;

            Vector3 fartPos = transform.position - (Vector3)moveDir * (transform.localScale.x / 2);
            Quaternion fartDir = Quaternion.LookRotation(Vector3.back, moveDir);
            GameObject fartObj = Instantiate(fartPrefab, fartPos, fartDir);
            fartObj.transform.localScale = Vector3.one * transform.localScale.x / 10;

            audioSource.PlayOneShot(fartSound);

            Shrink(true);

            lastFartTime = Time.time;
        }
        if (Time.time <= lastFartTime + fartCooldown)
        {
            float percentage = ((Time.time - lastFartTime) / fartCooldown);
            coolDown.fillAmount = percentage;
        }
        else
        {
            coolDown.fillAmount = 1;
        }
    }

    public void Grow()
    {
        audioSource.PlayOneShot(eatFood);
        ChangeScale(scaleGrowth);
    }

    public void Shrink(bool isFart)
    {
        ChangeScale(1 / scaleGrowth);

        if (!isFart)
        {
            audioSource.PlayOneShot(eatProbiotic);
        }
    }

    public void ChangeScale(float scaleChange)
    {
        if (transform.localScale.x * scaleChange < 3)
            return;
        transform.localScale *= scaleChange;

        if (transform.localScale.x < 12)
            blobbyMat.mainTexture = blobbyTextures[0];
        else if (transform.localScale.x >= 12 && transform.localScale.x < 30)
            blobbyMat.mainTexture = blobbyTextures[1];
        else if (transform.localScale.x >= 30)
            blobbyMat.mainTexture = blobbyTextures[2];

        foreach (GameObject obj in referencePoints)
        {
            SpringJoint2D[] joints = obj.GetComponents<SpringJoint2D>();
            joints[1].distance = transform.localScale.x / 7.5f;
        }
        foreach (SpringJoint2D joint in centerPoint.GetComponents<SpringJoint2D>())
        {
            joint.distance = transform.localScale.x / 3.8f

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

public class Blob : MonoBehaviour
{
    private class PropagateCollisions : MonoBehaviour
    {
        GameObject zPlayer;

        private void Start()
        {
            zPlayer = GameObject.FindGameObjectWithTag("PlayerMain");
        }

        void OnCollisionEnter2D(Collision2D collision)
        {
            if (collision.transform.tag == "Sticky")
            {
                gameObject.GetComponent<Rigidbody2D>().constraints =
                    RigidbodyConstraints2D.FreezePositionY
                    | RigidbodyConstraints2D.FreezePositionX
                    | RigidbodyConstraints2D.FreezeRotation;
            }
        }

        private void OnTriggerEnter2D(Collider2D collision)
        {
            if (collision.transform.tag == "DetachOne")
            {
                gameObject.GetComponent<Rigidbody2D>().constraints = RigidbodyConstraints2D.None;
            }
            if (collision.transform.tag == "DetachAll")
            {
                zPlayer.GetComponent<Blob>().TrigThis();
            }
            if (collision.transform.tag == "Virus")
            {
                Blob blob = zPlayer.GetComponent<Blob>();
                Destroy(collision.gameObject);
                blob.Grow();
                StatsManager.Instance.IncVirusCount();
            }

            if (collision.transform.tag == "Medicine")
            {
                Blob blob = zPlayer.GetComponent<Blob>();
                Destroy(collision.gameObject);
                // audioSource.PlayOneShot(eatProbiotic);
                blob.Shrink(false);
                StatsManager.Instance.IncVirusCount();
            }
        }
    }

    public int width = 5;
    public int height = 5;
    public int referencePointsCount = 12;
    public float referencePointRadius = 0.25f;
    public float mappingDetail = 10;
    public float springDampingRatio = 0;
    public float springFrequency = 2;
    public float scaleGrowth = 1.05f;
    public float fartForce = 250;
    public float fartCooldown = 5;
    public PhysicsMaterial2D surfaceMaterial;
    public Rigidbody2D[] allReferencePoints;
    public Image coolDown;
    public GameObject fartPrefab;
    public AudioSource audioSource;
    public AudioClip fartSound;
    public AudioClip eatFood;
    public AudioClip eatProbiotic;
    public Material blobbyMat;
    public List<Texture> blobbyTextures;
    public GameObject[] referencePoints { private set; get; }
    public GameObject centerPoint { private set; get; }
    int vertexCount;
    Vector3[] vertices;
    int[] triangles;
    Vector2[] uv;
    Vector3[,] offsets;
    float[,] weights;
    float lastFartTime;

    void Start()
    {
        CreateReferencePoints();
        CreateCenterPoint();
        IgnoreCollisionsBetweenReferencePoints();
        CreateMesh();
        MapVerticesToReferencePoints();

        ChangeScale(-2.5f);
        lastFartTime = -fartCooldown;
        blobbyMat.mainTexture = blobbyTextures[0];
        GameManager.Instance.state = GameState.IN_GAME;
    }

    void CreateReferencePoints()
    {
        Rigidbody2D rigidbody = GetComponent<Rigidbody2D>();
        referencePoints = new GameObject[referencePointsCount];
        Vector3 offsetFromCenter = (0.5f - referencePointRadius) * Vector3.up;
        float angle = 360.0f / referencePointsCount;

        for (int i = 0; i < referencePointsCount; i++)
        {
            referencePoints[i] = new GameObject { tag = gameObject.tag };
            referencePoints[i].AddComponent<PropagateCollisions>();
            referencePoints[i].transform.parent = transform;

            Quaternion rotation = Quaternion.AngleAxis(angle * (i - 1), Vector3.back);
            referencePoints[i].transform.localPosition = rotation * offsetFromCenter;

            Rigidbody2D body = referencePoints[i].AddComponent<Rigidbody2D>();
            body.constraints = RigidbodyConstraints2D.None;
            body.mass = 0.5f;
            body.interpolation = rigidbody.interpolation;
            body.collisionDetectionMode = rigidbody.collisionDetectionMode;
            body.drag = 0.5f;
            body.angularDrag = 0.5f;
            body.gravityScale = 0;
            allReferencePoints[i] = body;

            CircleCollider2D collider = referencePoints[i].AddComponent<CircleCollider2D>();
            collider.radius = referencePointRadius * (transform.localScale.x / 2);
            if (surfaceMaterial != null)
            {
                collider.sharedMaterial = surfaceMaterial;
            }

            AttachWithSpringJoint(referencePoints[i], gameObject, false);
            if (i > 0)
            {
                AttachWithSpringJoint(referencePoints[i], referencePoints[i - 1], false);
            }
        }
        AttachWithSpringJoint(referencePoints[0], referencePoints[referencePointsCount - 1], false);
    }

    void CreateCenterPoint()
    {
        // Create center point
        Rigidbody2D rigidbody = GetComponent<Rigidbody2D>();

        centerPoint = new GameObject();
        centerPoint.tag = gameObject.tag;
        centerPoint.AddComponent<PropagateCollisions>();
        centerPoint.transform.parent = transform;

        centerPoint.transform.localPosition = Vector3.zero;
        Rigidbody2D centerBody = centerPoint.AddComponent<Rigidbody2D>();
        centerBody.constraints = RigidbodyConstraints2D.None;
        centerBody.mass = 0.5f;
        centerBody.interpolation = rigidbody.interpolation;
        centerBody.collisionDetectionMode = rigidbody.collisionDetectionMode;
        centerBody.gravityScale = 0;

        CircleCollider2D centerCollider = centerPoint.AddComponent<CircleCollider2D>();
        centerCollider.radius = 3;
        if (surfaceMaterial != null)
        {
            centerCollider.sharedMaterial = surfaceMaterial;
        }

        for (int i = 0; i < referencePointsCount; i++)
        {
            AttachWithSpringJoint(centerPoint, referencePoints[i], true);
        }
    }

    void AttachWithSpringJoint(GameObject referencePoint, GameObject connected, bool isCenter)
    {
        SpringJoint2D springJoint = referencePoint.AddComponent<SpringJoint2D>();
        springJoint.connectedBody = connected.GetComponent<Rigidbody2D>();
        springJoint.connectedAnchor = LocalPosition(referencePoint) - LocalPosition(connected);
        springJoint.distance = isCenter ? width / 2 : 0;
        springJoint.dampingRatio = springDampingRatio;
        springJoint.frequency = springFrequency;
        springJoint.autoConfigureDistance = false;
    }

    void IgnoreCollisionsBetweenReferencePoints()
    {
        int i;
        int j;
        CircleCollider2D a;
        CircleCollider2D b;
        CircleCollider2D c = null;
        if (centerPoint != null)
            c = centerPoint.GetComponent<CircleCollider2D>();

        for (i = 0; i < referencePointsCount; i++)
        {
            for (j = i; j < referencePointsCount; j++)
            {
                a = referencePoints[i].GetComponent<CircleCollider2D>();
                b = referencePoints[j].GetComponent<CircleCollider2D>();
                Physics2D.IgnoreCollision(a, b, true);
            }
            if (c != null)
            {
                a = referencePoints[i].GetComponent<CircleCollider2D>();
                Physics2D.IgnoreCollision(a, c, true);
            }
        }
    }

    void CreateMesh()
    {
        vertexCount = (width + 1) * (height + 1);

        int trianglesCount = width * height * 6;
        vertices = new Vector3[vertexCount];
        triangles = new int[trianglesCount];
        uv = new Vector2[vertexCount];
        int t;

        for (int y = 0; y <= height; y++)
        {
            for (int x = 0; x <= width; x++)
            {
                int v = (width + 1) * y + x;
                vertices[v] = new Vector3(x / (float)width - 0.5f, y / (float)height - 0.5f, 0);
                uv[v] = new Vector2(x / (float)width, y / (float)height);

                if (x < width && y < height)
                {
                    t = 3 * (2 * width * y + 2 * x);

                    triangles[t] = v;
                    triangles[++t] = v + width + 1;
                    triangles[++t] = v + width + 2;
                    triangles[++t] = v;
                    triangles[++t] = v + width + 2;
                    triangles[++t] = v + 1;
                }
            }
        }

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.vertices = vertices;
        mesh.uv = uv;
        mesh.triangles = triangles;
    }

    void MapVerticesToReferencePoints()
    {
        offsets = new Vector3[vertexCount, referencePointsCount];
        weights = new float[vertexCount, referencePointsCount];

        for (int i = 0; i < vertexCount; i++)
        {
            float totalWeight = 0;

            for (int j = 0; j < referencePointsCount; j++)
            {
                offsets[i, j] = vertices[i] - LocalPosition(referencePoints[j]);
                weights[i, j] = 1 / Mathf.Pow(offsets[i, j].magnitude, mappingDetail);
                totalWeight += weights[i, j];
            }

            for (int j = 0; j < referencePointsCount; j++)
            {
                weights[i, j] /= totalWeight;
            }
        }
    }

    void Update()
    {
        UpdateVertexPositions();
        CheckFart();
        CheckSize();
    }

    private void CheckSize()
    {
        if (gameObject.transform.localScale.x >= StatsManager.maxSize)
        {
            GameManager.Instance.GoLost();
        }
    }

    void UpdateVertexPositions()
    {
        Vector3[] vertices = new Vector3[vertexCount];

        for (int i = 0; i < vertexCount; i++)
        {
            vertices[i] = Vector3.zero;

            for (int j = 0; j < referencePointsCount; j++)
            {
                vertices[i] += weights[i, j] * (LocalPosition(referencePoints[j]) + offsets[i, j]);
            }
        }

        Mesh mesh = GetComponent<MeshFilter>().mesh;
        mesh.vertices = vertices;
        mesh.RecalculateBounds();
    }

    Vector3 LocalPosition(GameObject obj)
    {
        return transform.InverseTransformPoint(obj.transform.position);
    }

    public void TrigThis()
    {
        int z = 0;
        foreach (Rigidbody2D child in allReferencePoints)
        {
            allReferencePoints[z].constraints = RigidbodyConstraints2D.None;
            z++;
        }
    }

    private void CheckFart()
    {
        if (Input.GetMouseButtonDown(0) && Time.time > lastFartTime + fartCooldown && GameManager.Instance.GetGameStates() == GameState.IN_GAME)
        {
            Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            mousePos.z = transform.position.z;

            Vector2 moveDir = -(mousePos - transform.position).normalized;
            centerPoint.GetComponent<Rigidbody2D>().velocity += moveDir * fartForce;

            Vector3 fartPos = transform.position - (Vector3)moveDir * (transform.localScale.x / 2);
            Quaternion fartDir = Quaternion.LookRotation(Vector3.back, moveDir);
            GameObject fartObj = Instantiate(fartPrefab, fartPos, fartDir);
            fartObj.transform.localScale = Vector3.one * transform.localScale.x / 10;

            audioSource.PlayOneShot(fartSound);

            Shrink(true);

            lastFartTime = Time.time;
        }
        if (Time.time <= lastFartTime + fartCooldown)
        {
            float percentage = ((Time.time - lastFartTime) / fartCooldown);
            coolDown.fillAmount = percentage;
        }
        else
        {
            coolDown.fillAmount = 1;
        }
    }

    public void Grow()
    {
        audioSource.PlayOneShot(eatFood);
        ChangeScale(scaleGrowth);
    }

    public void Shrink(bool isFart)
    {
        ChangeScale(1 / scaleGrowth);

        if (!isFart)
        {
            audioSource.PlayOneShot(eatProbiotic);
        }
    }

    public void ChangeScale(float scaleChange)
    {
        if (transform.localScale.x * scaleChange < 3)
            return;
        transform.localScale *= scaleChange;

        if (transform.localScale.x < 12)
            blobbyMat.mainTexture = blobbyTextures[0];
        else if (transform.localScale.x >= 12 && transform.localScale.x < 30)
            blobbyMat.mainTexture = blobbyTextures[1];
        else if (transform.localScale.x >= 30)
            blobbyMat.mainTexture = blobbyTextures[2];

        foreach (GameObject obj in referencePoints)
        {
            SpringJoint2D[] joints = obj.GetComponents<SpringJoint2D>();
            joints[1].distance = transform.localScale.x / 7.5f;
        }
        foreach (SpringJoint2D joint in centerPoint.GetComponents<SpringJoint2D>())
        {
            joint.distance = transform.localScale.x / 3.8f

food generation

food generation

food generation

Gameplay involves spawning objects —— categorized as Junk Food or Probiotics —— around the screen's edges, where they bounce within the petri dish. To manage this, the system:

  • Identifies screen boundaries based on aspect ratio.

  • Spawns objects with their sprites.

  • Tracks edge collisions, destroying objects after a set number of bounces.
    The Edible class handles these mechanics, managing spawning, movement, and collisions.

Gameplay involves spawning objects —— categorized as Junk Food or Probiotics —— around the screen's edges, where they bounce within the petri dish. To manage this, the system:

  • Identifies screen boundaries based on aspect ratio.

  • Spawns objects with their sprites.

  • Tracks edge collisions, destroying objects after a set number of bounces.
    The Edible class handles these mechanics, managing spawning, movement, and collisions.

Gameplay involves spawning objects —— categorized as Junk Food or Probiotics —— around the screen's edges, where they bounce within the petri dish. To manage this, the system:

  • Identifies screen boundaries based on aspect ratio.

  • Spawns objects with their sprites.

  • Tracks edge collisions, destroying objects after a set number of bounces.
    The Edible class handles these mechanics, managing spawning, movement, and collisions.

void Update()
{
    Vector3 moveVector = dir * moveSpeed * Time.deltaTime;
    transform.position = transform.position + moveVector;


    if (CheckPointIsInArena(transform.position))
        passedCenter = true;
    if (passedCenter && transform.position.magnitude > destroyRadius)
    {
        Destroy(gameObject);
    }


    transform.rotation *= Quaternion.AngleAxis(spinSpeed * Time.deltaTime, Vector3.forward);
}


void OnTriggerEnter2D(Collider2D collision)
{
    if (passedCenter && bounceCount > 0)
    {
        if (collision.gameObject.name.StartsWith("Left") || collision.gameObject.name.StartsWith("Right"))
        {
            dir.x *= -1;
            bounceCount--;
        }
        if (collision.gameObject.name.StartsWith("Top") || collision.gameObject.name.StartsWith("Bottom"))
        {
            dir.y *= -1;
            bounceCount--;
        }
    }
}


void UpdateScreenDimensions()
{
    float aspect = (float)Screen.width / Screen.height;
    screenHeightWorld = Camera.main.orthographicSize * 2;
    screenWidthWorld = screenHeightWorld * aspect;
}


public void Throw(float speed)
{
    if (gameObject.CompareTag("Probiotics"))
    {
        speed /= 2;
    }


    UpdateScreenDimensions();


    float angle = Random.Range(0, 2 * Mathf.PI);


    float x = Mathf.Cos(angle) * Mathf.Abs(screenHeightWorld / 2 / Mathf.Sin(angle));
    float y = Mathf.Sin(angle) * Mathf.Abs(screenWidthWorld / 2 / Mathf.Cos(angle));
    x = Mathf.Clamp(x, -screenWidthWorld / 2, screenWidthWorld / 2);
    y = Mathf.Clamp(y, -screenHeightWorld / 2, screenHeightWorld / 2);


    Vector2 spawnPos = new Vector2(x, y);
    transform.position = spawnPos;


    moveSpeed = speed;


    Vector2 perpendicularDir = Vector2.Perpendicular(spawnPos - arenaOffset).normalized;
    Vector2 targetPos = arenaOffset + perpendicularDir * arenaRadius * Random.Range(-1f, 1f);


    dir = (targetPos - spawnPos).normalized

void Update()
{
    Vector3 moveVector = dir * moveSpeed * Time.deltaTime;
    transform.position = transform.position + moveVector;


    if (CheckPointIsInArena(transform.position))
        passedCenter = true;
    if (passedCenter && transform.position.magnitude > destroyRadius)
    {
        Destroy(gameObject);
    }


    transform.rotation *= Quaternion.AngleAxis(spinSpeed * Time.deltaTime, Vector3.forward);
}


void OnTriggerEnter2D(Collider2D collision)
{
    if (passedCenter && bounceCount > 0)
    {
        if (collision.gameObject.name.StartsWith("Left") || collision.gameObject.name.StartsWith("Right"))
        {
            dir.x *= -1;
            bounceCount--;
        }
        if (collision.gameObject.name.StartsWith("Top") || collision.gameObject.name.StartsWith("Bottom"))
        {
            dir.y *= -1;
            bounceCount--;
        }
    }
}


void UpdateScreenDimensions()
{
    float aspect = (float)Screen.width / Screen.height;
    screenHeightWorld = Camera.main.orthographicSize * 2;
    screenWidthWorld = screenHeightWorld * aspect;
}


public void Throw(float speed)
{
    if (gameObject.CompareTag("Probiotics"))
    {
        speed /= 2;
    }


    UpdateScreenDimensions();


    float angle = Random.Range(0, 2 * Mathf.PI);


    float x = Mathf.Cos(angle) * Mathf.Abs(screenHeightWorld / 2 / Mathf.Sin(angle));
    float y = Mathf.Sin(angle) * Mathf.Abs(screenWidthWorld / 2 / Mathf.Cos(angle));
    x = Mathf.Clamp(x, -screenWidthWorld / 2, screenWidthWorld / 2);
    y = Mathf.Clamp(y, -screenHeightWorld / 2, screenHeightWorld / 2);


    Vector2 spawnPos = new Vector2(x, y);
    transform.position = spawnPos;


    moveSpeed = speed;


    Vector2 perpendicularDir = Vector2.Perpendicular(spawnPos - arenaOffset).normalized;
    Vector2 targetPos = arenaOffset + perpendicularDir * arenaRadius * Random.Range(-1f, 1f);


    dir = (targetPos - spawnPos).normalized


Level progression is tied to the number of floating entities, achieved by reducing spawn intervals over time. To balance gameplay during the game jam, extensive playtesting fixed the spawn ratio at 1 probiotic per 9 junk food. The EdibleSpawner class manages spawn rates and adjusts difficulty dynamically as the game progresses.


Level progression is tied to the number of floating entities, achieved by reducing spawn intervals over time. To balance gameplay during the game jam, extensive playtesting fixed the spawn ratio at 1 probiotic per 9 junk food. The EdibleSpawner class manages spawn rates and adjusts difficulty dynamically as the game progresses.


On top of that, we also have to handle level progression as time passes, and the spawn ratio between the probiotics and the junk food. To better control the level flow and allow players in the game jam to have a better gaming experience in the short run, we decided to fix the spawn ratio to 1 probiotic per 9 junk food after robust playtesting. Level progression is represented by the increasing number of entities that are floating in the petri dish, which is done by decreasing the time interval between spawns. Below is the class EdibleSpawner, a class that handles the spawning rate and adjusts the level difficulty as time passes:

void Start()
{
    gameStartTime = Time.time;
}

void Update()
{
    if (Time.time >= lastThrowTime + throwTimeInterval)
    {
        ThrowEdible();
        lastThrowTime = Time.time;
    }


    throwTimeInterval = -(Time.time - gameStartTime) / 100 + startThrowTimeInterval;
    throwTimeInterval = Mathf.Max(throwTimeInterval, 0.3f);
    speed = (Time.time - gameStartTime) / 20 + startSpeed;
    speed = Mathf.Min(speed, 20);
}


private void ThrowEdible()
{
    GameObject edibleItem =
        (Random.Range(0, 10) >= 9) ? Instantiate(probiotics) : Instantiate(junkFood);
    edibleItem.GetComponent<Edibles>().Throw(speed

void Start()
{
    gameStartTime = Time.time;
}

void Update()
{
    if (Time.time >= lastThrowTime + throwTimeInterval)
    {
        ThrowEdible();
        lastThrowTime = Time.time;
    }


    throwTimeInterval = -(Time.time - gameStartTime) / 100 + startThrowTimeInterval;
    throwTimeInterval = Mathf.Max(throwTimeInterval, 0.3f);
    speed = (Time.time - gameStartTime) / 20 + startSpeed;
    speed = Mathf.Min(speed, 20);
}


private void ThrowEdible()
{
    GameObject edibleItem =
        (Random.Range(0, 10) >= 9) ? Instantiate(probiotics) : Instantiate(junkFood);
    edibleItem.GetComponent<Edibles>().Throw(speed

Copyright © 2024, Andy Pang. All rights reserved.

Copyright © 2024, Andy Pang. All rights reserved.

Copyright © 2024, Andy Pang. All rights reserved.