Cum să faci un joc cu șarpe în Unity
În această postare, voi arăta cum să creez un clasic Snake Game în Unity.
Unity versiune folosită în acest tutorial: Unity 2018.3.0f2 (64 de biți)
Pasul 1: Creați Scriptul
Fiind un "One Script Game", acest tutorial necesită doar 1 script:
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);
}
}
}
Scriptul de mai sus creează o grilă de quad-uri primitive, apoi le schimbă materialele într-unul dintre cele patru: un material de fundal, un material cu cap de șarpe, un material pentru corp de șarpe sau un material de măr. De asemenea, plasează automat camera direct deasupra sistemului de grilă și își schimbă dimensiunea ortografică pentru a încapsula limitele colective ale tuturor blocurilor.
Pasul 2: Configurați jocul Snake
Acum, să setăm jocul Snake folosind scriptul de mai sus:
- Creați o scenă nouă
- Schimbați rezoluția vizualizării joc, astfel încât lățimea și înălțimea să fie egale (de ex. 600px x 600px)
- Creați un nou GameObject (GameObject -> Create Empty) și denumiți-l "_GameGenerator"
- Atașați scriptul SC_SnakeGameGenerator.cs la obiectul _GameGenerator
După cum veți observa, SC_SnakeGameGenerator are câteva variabile care trebuie alocate:
- Variabila Main Camera se explică de la sine, atribuiți camera principală implicită.
- Acum, pentru materiale, creați 4 materiale (Click dreapta -> Creare -> Material) și denumiți-le respectiv "ground_material", "snake_material", "head_material" și "fruit_material":
Pentru ground_material schimbați Shader-ul în Unlit/Color și schimbați Main Color în negru:
Pentru celelalte 3 Materiale schimbați Shader-ul la Unlit/Texture și atribuiți texturile de mai jos:
Pentru snake_material:
Pentru materialul_capului:
Pentru fruit_material:
- Atribuiți materialele variabilelor
Acum este timpul să apăsați pe Play și să testați jocul:
Totul funcționează conform așteptărilor, acum aveți un joc cu șarpe în Unity.