איך לעשות משחק נחש באחדות

בפוסט הזה, אני אראה כיצד ליצור משחק נחש קלאסי ב-Unity.

Sharp Coder נגן וידאו

נסה זאת בעצמך

Unity גרסה בשימוש במדריך זה: Unity 2018.3.0f2 (64 סיביות)

שלב 1: צור את הסקריפט

בהיותך "One Script Game" המדריך הזה דורש רק סקריפט אחד:

SC_SnakeGameGenerator.cs

//You are free to use this script in Free or Commercial projects
//sharpcoderblog.com @2019

using System.Collections.Generic;
using UnityEngine;

public class SC_SnakeGameGenerator : MonoBehaviour
{
    //Game area resolution, the higher number means more blocks
    public int areaResolution = 22;
    //Snake movement speed
    public float snakeSpeed = 10f;
    //Main Camera
    public Camera mainCamera;
    //Materials
    public Material groundMaterial;
    public Material snakeMaterial;
    public Material headMaterial;
    public Material fruitMaterial;

    //Grid system
    Renderer[] gameBlocks;
    //Snake coordenates
    List<int> snakeCoordinates = new List<int>();
    enum Direction { Up, Down, Left, Right };
    Direction snakeDirection = Direction.Right;
    float timeTmp = 0;
    //Block where the fruit is placed
    int fruitBlockIndex = -1;
    //Total accumulated points
    int totalPoints = 0;
    //Game status
    bool gameStarted = false;
    bool gameOver = false;
    //Camera scaling
    Bounds targetBounds;
    //Text styling
    GUIStyle mainStyle = new GUIStyle();

    // Start is called before the first frame update
    void Start()
    {
        //Generate play area
        gameBlocks = new Renderer[areaResolution * areaResolution];
        for (int x = 0; x < areaResolution; x++)
        {
            for (int y = 0; y < areaResolution; y++)
            {
                GameObject quadPrimitive = GameObject.CreatePrimitive(PrimitiveType.Quad);
                quadPrimitive.transform.position = new Vector3(x, 0, y);
                Destroy(quadPrimitive.GetComponent<Collider>());
                quadPrimitive.transform.localEulerAngles = new Vector3(90, 0, 0);
                quadPrimitive.transform.SetParent(transform);
                gameBlocks[(x * areaResolution) + y] = quadPrimitive.GetComponent<Renderer>();
                targetBounds.Encapsulate(gameBlocks[(x * areaResolution) + y].bounds);
            }
        }

        //Scale the MainCamera to fit the game blocks
        mainCamera.transform.eulerAngles = new Vector3(90, 0, 0);
        mainCamera.orthographic = true;
        float screenRatio = (float)Screen.width / (float)Screen.height;
        float targetRatio = targetBounds.size.x / targetBounds.size.y;

        if (screenRatio >= targetRatio)
        {
            mainCamera.orthographicSize = targetBounds.size.y / 2;
        }
        else
        {
            float differenceInSize = targetRatio / screenRatio;
            mainCamera.orthographicSize = targetBounds.size.y / 2 * differenceInSize;
        }
        mainCamera.transform.position = new Vector3(targetBounds.center.x, targetBounds.center.y + 1, targetBounds.center.z);

        //Generate the Snake with 3 blocks
        InitializeSnake();
        ApplyMaterials();

        mainStyle.fontSize = 24;
        mainStyle.alignment = TextAnchor.MiddleCenter;
        mainStyle.normal.textColor = Color.white;
    }

    void InitializeSnake()
    {
        snakeCoordinates.Clear();
        int firstlock = Random.Range(0, areaResolution - 1) + (areaResolution * 3);
        snakeCoordinates.Add(firstlock);
        snakeCoordinates.Add(firstlock - areaResolution);
        snakeCoordinates.Add(firstlock - (areaResolution * 2));

        gameBlocks[snakeCoordinates[0]].transform.localEulerAngles = new Vector3(90, 90, 0);
        fruitBlockIndex = -1;
        timeTmp = 1;
        snakeDirection = Direction.Right;
        totalPoints = 0;
    }

    // Update is called once per frame
    void Update()
    {
        if (!gameStarted)
        {
            if (Input.anyKeyDown)
            {
                gameStarted = true;
            }
            return;
        }
        if (gameOver)
        {
            //Flicker the snake blocks
            if (timeTmp < 0.44f)
            {
                timeTmp += Time.deltaTime;
            }
            else
            {
                timeTmp = 0;
                for (int i = 0; i < snakeCoordinates.Count; i++)
                {
                    if (gameBlocks[snakeCoordinates[i]].sharedMaterial == groundMaterial)
                    {
                        gameBlocks[snakeCoordinates[i]].sharedMaterial = (i == 0 ? headMaterial : snakeMaterial);
                    }
                    else
                    {
                        gameBlocks[snakeCoordinates[i]].sharedMaterial = groundMaterial;
                    }
                }
            }

            if (Input.GetKeyDown(KeyCode.Space))
            {
                InitializeSnake();
                ApplyMaterials();
                gameOver = false;
                gameStarted = false;
            }
        }
        else
        {
            if (timeTmp < 1)
            {
                timeTmp += Time.deltaTime * snakeSpeed;
            }
            else
            {
                timeTmp = 0;
                if (snakeDirection == Direction.Right || snakeDirection == Direction.Left)
                {
                    //Detect if the Snake hit the sides
                    if (snakeDirection == Direction.Left && snakeCoordinates[0] < areaResolution)
                    {
                        gameOver = true;
                        return;
                    }
                    else if (snakeDirection == Direction.Right && snakeCoordinates[0] >= (gameBlocks.Length - areaResolution))
                    {
                        gameOver = true;
                        return;
                    }

                    int newCoordinate = snakeCoordinates[0] + (snakeDirection == Direction.Left ? -areaResolution : areaResolution);
                    //Snake has ran into itself, game over
                    if (snakeCoordinates.Contains(newCoordinate))
                    {
                        gameOver = true;
                        return;
                    }
                    if (newCoordinate < gameBlocks.Length)
                    {
                        for (int i = snakeCoordinates.Count - 1; i > 0; i--)
                        {
                            snakeCoordinates[i] = snakeCoordinates[i - 1];
                        }
                        snakeCoordinates[0] = newCoordinate;
                        gameBlocks[snakeCoordinates[0]].transform.localEulerAngles = new Vector3(90, (snakeDirection == Direction.Left ? -90 : 90), 0);
                    }
                }
                else if (snakeDirection == Direction.Up || snakeDirection == Direction.Down)
                {
                    //Detect if snake hits the top or bottom
                    if (snakeDirection == Direction.Up && (snakeCoordinates[0] + 1) % areaResolution == 0)
                    {
                        gameOver = true;
                        return;
                    }
                    else if (snakeDirection == Direction.Down && (snakeCoordinates[0] + 1) % areaResolution == 1)
                    {
                        gameOver = true;
                        return;
                    }

                    int newCoordinate = snakeCoordinates[0] + (snakeDirection == Direction.Down ? -1 : 1);
                    //Snake has ran into itself, game over
                    if (snakeCoordinates.Contains(newCoordinate))
                    {
                        gameOver = true;
                        return;
                    }
                    if (newCoordinate < gameBlocks.Length)
                    {
                        for (int i = snakeCoordinates.Count - 1; i > 0; i--)
                        {
                            snakeCoordinates[i] = snakeCoordinates[i - 1];
                        }
                        snakeCoordinates[0] = newCoordinate;
                        gameBlocks[snakeCoordinates[0]].transform.localEulerAngles = new Vector3(90, (snakeDirection == Direction.Down ? 180 : 0), 0);
                    }
                }

                ApplyMaterials();
            }

            if (Input.GetKeyDown(KeyCode.RightArrow))
            {
                int newCoordinate = snakeCoordinates[0] + areaResolution;
                if (!ContainsCoordinate(newCoordinate))
                {
                    snakeDirection = Direction.Right;
                }
            }
            if (Input.GetKeyDown(KeyCode.LeftArrow))
            {
                int newCoordinate = snakeCoordinates[0] - areaResolution;
                if (!ContainsCoordinate(newCoordinate))
                {
                    snakeDirection = Direction.Left;
                }
            }
            if (Input.GetKeyDown(KeyCode.UpArrow))
            {
                int newCoordinate = snakeCoordinates[0] + 1;
                if (!ContainsCoordinate(newCoordinate))
                {
                    snakeDirection = Direction.Up;
                }
            }
            if (Input.GetKeyDown(KeyCode.DownArrow))
            {
                int newCoordinate = snakeCoordinates[0] - 1;
                if (!ContainsCoordinate(newCoordinate))
                {
                    snakeDirection = Direction.Down;
                }
            }
        }

        if (fruitBlockIndex < 0)
        {
            //Place a fruit block
            int indexTmp = Random.Range(0, gameBlocks.Length - 1);

            //Check if the block is not occupied with a snake block
            for (int i = 0; i < snakeCoordinates.Count; i++)
            {
                if (snakeCoordinates[i] == indexTmp)
                {
                    indexTmp = -1;
                    break;
                }
            }

            fruitBlockIndex = indexTmp;
        }
    }

    void ApplyMaterials()
    {
        //Apply Snake material
        for (int i = 0; i < gameBlocks.Length; i++)
        {
            gameBlocks[i].sharedMaterial = groundMaterial;
            bool fruitPicked = false;
            for (int a = 0; a < snakeCoordinates.Count; a++)
            {
                if (snakeCoordinates[a] == i)
                {
                    gameBlocks[i].sharedMaterial = (a == 0 ? headMaterial : snakeMaterial);
                }
                if (snakeCoordinates[a] == fruitBlockIndex)
                {
                    //Pick a fruit
                    fruitPicked = true;
                }
            }
            if (fruitPicked)
            {
                fruitBlockIndex = -1;
                //Add new block
                int snakeBlockRotationY = (int)gameBlocks[snakeCoordinates[snakeCoordinates.Count - 1]].transform.localEulerAngles.y;
                //print(snakeBlockRotationY);
                if (snakeBlockRotationY == 270)
                {
                    snakeCoordinates.Add(snakeCoordinates[snakeCoordinates.Count - 1] + areaResolution);
                }
                else if (snakeBlockRotationY == 90)
                {
                    snakeCoordinates.Add(snakeCoordinates[snakeCoordinates.Count - 1] - areaResolution);
                }
                else if (snakeBlockRotationY == 0)
                {
                    snakeCoordinates.Add(snakeCoordinates[snakeCoordinates.Count - 1] + 1);
                }
                else if (snakeBlockRotationY == 180)
                {
                    snakeCoordinates.Add(snakeCoordinates[snakeCoordinates.Count - 1] - 1);
                }
                totalPoints++;
            }
            if (i == fruitBlockIndex)
            {
                gameBlocks[i].sharedMaterial = fruitMaterial;
                gameBlocks[i].transform.localEulerAngles = new Vector3(90, 0, 0);
            }
        }
    }

    bool ContainsCoordinate(int coordinate)
    {
        for (int i = 0; i < snakeCoordinates.Count; i++)
        {
            if (snakeCoordinates[i] == coordinate)
            {
                return true;
            }
        }

        return false;
    }

    void OnGUI()
    {
        //Display Player score and other info 
        if (gameStarted)
        {
            GUI.Label(new Rect(Screen.width / 2 - 100, 5, 200, 20), totalPoints.ToString(), mainStyle);
        }
        else
        {
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 10, 200, 20), "Press Any Key to Play\n(Use Arrows to Change Direction)", mainStyle);
        }
        if (gameOver)
        {
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 20, 200, 40), "Game Over\n(Press 'Space' to Restart)", mainStyle);
        }
    }
}

התסריט שלמעלה יוצר רשת של ארבעים פרימיטיביים, ואז משנה את החומרים שלהם לאחד מהארבעה: חומר רקע, חומר ראש נחש, חומר גוף נחש או חומר תפוח. זה גם ממקם את המצלמה באופן אוטומטי ישירות מעל מערכת הרשת ומשנה את הגודל האורתוגרפי שלה כדי להכיל את הגבולות הקולקטיביים של כל הבלוקים.

שלב 2: הגדר את משחק הנחש

עכשיו בואו נגדיר את משחק Snake באמצעות הסקריפט שלמעלה:

  • צור סצנה חדשה
  • שנה את רזולוציית תצוגת המשחק כך שהרוחב והגובה יהיו שווים (לדוגמה 600px x 600px)

  • צור GameObject חדש (GameObject -> Create Empty) ושם לו "_GameGenerator"
  • צרף SC_SnakeGameGenerator.cs סקריפט ל-_GameGenerator Object

כפי שתבחין ל-SC_SnakeGameGenerator יש כמה משתנים שצריך להקצות:

  • המשתנה Main Camera מובן מאליו, הקצה את ברירת המחדל של המצלמה הראשית.
  • כעת עבור חומרים, צור 4 חומרים (לחץ ימני -> צור -> חומר) ושם אותם בהתאמה "ground_material", "snake_material", "head_material" ו-"fruit_material":

עבור ground_material שנה את ההצללה שלו ל- Unlit/Color ושנה את הצבע הראשי לשחור:

עבור שאר החומרים 3 שנה את ה-Shader ל-Unlit/Texture והקצה את הטקסטורות למטה:

עבור חומר_נחש:

עבור head_material:

עבור חומר_פרי:

  • שיוך את החומרים למשתנים

עכשיו הגיע הזמן ללחוץ על Play ולבדוק את המשחק:

הכל עובד כמצופה, עכשיו יש לך משחק נחשים שניתן לשחק ב-Unity.