This article is the relevant details of the Unity3D Snake game from production to deployment.
Project open source code: https://github.com/zstar1003/3D_Snake
Trial link: http://xdxsb.top/Snake_Game_3D
Effect preview:
The content in the trial link will be slightly different from this rendering, which will be explained in detail later.
Game rules
Classic snake game: The snake’s body continues to grow as it eats more food. Use A/D or the direction keys ←→ to control the direction. If the snake’s head hits the snake or the surrounding walls, the game will fail.
Snake body control and collision detection
The logic of snake control and collision detection is written in the SnakeController.cs
file.
The idea of the snake head movement is to continuously move the snake head in the forward
direction, and the forward speed is equal to the speed value x the current time. At the same time, a list is used to record the historical trajectory of the snake head movement, and the snake body moves through this trajectory.
In order to distinguish whether the extended snake body is the original snake body or the newly extended snake body, the newly extended snake body is given a Block label. Failure to distinguish will result in a snake head and snake body collision being triggered as soon as the collision begins, causing the game to end. .
Complete code:
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; public class SnakeController : MonoBehaviour {<!-- --> // set up public float moveSpeed = 5f; public float steerSpeed = 180f; public float bodySpeed = 5f; public int Gap = 10; // Prefab public GameObject bodyPrefab; //body component // collection of body components private List<GameObject> _bodyParts = new List<GameObject>(); private List<Vector3> _positionHistory = new List<Vector3>(); //music controller public AudioController audioController; private void Start() {<!-- --> addBodyPart(); audioController = GameObject.FindGameObjectWithTag("Audio").GetComponent<AudioController>(); } private void Update() {<!-- --> // Move forward transform.position + = transform.forward * moveSpeed * Time.deltaTime; // direction control float steerDirection = Input.GetAxis("Horizontal"); // Return value from -1 to 1 transform.Rotate(Vector3.up * steerDirection * steerSpeed * Time.deltaTime); //Save location movement history _positionHistory.Insert(0, transform.position); //Move the body component int index = 0; foreach (var body in _bodyParts) {<!-- --> Vector3 point = _positionHistory[Mathf.Clamp(index * Gap, 0, _positionHistory.Count - 1)]; // Let the snake's body components move along the movement trajectory of the head Vector3 moveDirection = point - body.transform.position; body.transform.position + = moveDirection * bodySpeed * Time.deltaTime; // Let the body component move in the direction of the head body.transform.LookAt(point); index + + ; } } // Snake body extension private void addBodyPart() {<!-- --> GameObject body = Instantiate(bodyPrefab, new Vector3(0, transform.position.y, 0), Quaternion.identity); _bodyParts.Add(body); } //The body added later is marked with a Block tag private void addBodyPart_Block() {<!-- --> GameObject body = Instantiate(bodyPrefab, new Vector3(0, _bodyParts.Last().transform.position.y, 0), Quaternion.identity); body.tag = "Block"; _bodyParts.Add(body); } //trigger detection private void OnTriggerEnter(Collider other) {<!-- --> if (other.tag == "Food") {<!-- --> //Must be deleted first, otherwise it will cause multiple triggers Destroy(other.gameObject); addBodyPart_Block(); GameObject.Find("SpawnPoint").GetComponent<SpawnItem>().SpawnItems(); audioController.PlaySfx(audioController.eat); } else if (other.tag == "Block") {<!-- --> SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); } } }
Food rotation
Controlling food rotation is relatively simple, just add Rotate in update.
Food.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Food : MonoBehaviour { void Start() { } void Update() { //Rotate transform.Rotate(Vector3.up); } }
Food is randomly generated
I did not use random numbers to randomly generate food, and problems are prone to occur in three-dimensional scenes. Therefore, 6 food generation points are added to the scene. When the food is triggered, new food is generated at a random point.
SpawnItem.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SpawnItem : MonoBehaviour { public Transform[] SpawnPoints; public float spawnTime = 2.5f; public GameObject Items; void Start() { } void Update() { } public void SpawnItems() { int spawnIndex = Random.Range(0, SpawnPoints.Length); Instantiate(Items, SpawnPoints[spawnIndex].position, SpawnPoints[spawnIndex].rotation); } }
Scene switching
Here, different scenes are used to isolate the game start interface and end interface, and only one line of code is needed to switch:
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
The Index here is the serial number sequence of the scenes when packaging.
Local WebGL testing
After packaging with WebGL, you will get 3 folders and an index.html file. If you open index.html directly, an error will be reported and you need to use the server mode to run it.
First, configure server-related components on Win10. Refer to the previous blog post [Practical Tips] Build a LAN FTP server on Win10.
Then create a new file web.config
under the packaged folder and enter the following content:
<?xml version="1.0" encoding="utf-8"?> <!-- For more information on how to configure an ASP.NET application, visit https://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.webServer> <httpProtocol> <!-- Allow cross-domain configuration --> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="X-Requested-With,Content-Type,Authorization" /> <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE,OPTIONS" /> <add name="Access-Control-Allow-Credentials" value="true" /> </customHeaders> </httpProtocol> <staticContent> <remove fileExtension=".mem" /> <remove fileExtension=".data" /> <remove fileExtension=".unity3d" /> <remove fileExtension=".jsbr" /> <remove fileExtension=".membr" /> <remove fileExtension=".databr" /> <remove fileExtension=".unity3dbr" /> <remove fileExtension=".jsgz" /> <remove fileExtension=".memgz" /> <remove fileExtension=".datagz" /> <remove fileExtension=".unity3dgz" /> <remove fileExtension=".json" /> <remove fileExtension=".unityweb" /> <mimeMap fileExtension=".mem" mimeType="application/octet-stream" /> <mimeMap fileExtension=".data" mimeType="application/octet-stream" /> <mimeMap fileExtension=".unity3d" mimeType="application/octet-stream" /> <mimeMap fileExtension=".jsbr" mimeType="application/octet-stream" /> <mimeMap fileExtension=".membr" mimeType="application/octet-stream" /> <mimeMap fileExtension=".databr" mimeType="application/octet-stream" /> <mimeMap fileExtension=".unity3dbr" mimeType="application/octet-stream" /> <mimeMap fileExtension=".jsgz" mimeType="application/x-javascript; charset=UTF-8" /> <mimeMap fileExtension=".memgz" mimeType="application/octet-stream" /> <mimeMap fileExtension=".datagz" mimeType="application/octet-stream" /> <mimeMap fileExtension=".unity3dgz" mimeType="application/octet-stream" /> <mimeMap fileExtension=".json" mimeType="application/json; charset=UTF-8" /> <mimeMap fileExtension=".unityweb" mimeType="application/octet-stream" /> </staticContent> </system.webServer> </configuration>
Then in IIS, create a new http server and choose an unoccupied port. I chose port 8080 here.
After opening the website, enter http://localhost:8080/
in the browser to access the test.
Github deployment
Github deployment is very easy, just create a new warehouse and upload the packaged content directly.
Then select the main branch in Settings/Pages, click Save, and the access URL will appear above in a few minutes.
Remaining issues: inconsistent testing before and after packaging
At present, the project runs normally when untiy is run and tested, but when packaging webgl or exe, the snake body is separated. After looking at some packaging options, the problem has not been solved. Readers who know about this problem are welcome to comment. District exchange.