[Unity] 3D Snake game production/WebGL local testing and project deployment

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.