本文是Unity3D贪吃蛇游戏从制作到部署的相关细节项目开源代码:https://github.com/zstar1003/3D_Snake 试玩链接:http://xdxsb.top/Snake_Game_3D效果预览:试玩链接中的内容会和该效果图略有不同,后面会详细说明。
游戏规则
经典贪吃蛇游戏:蛇身随着吃食物的增加不断变长,通过A/D或方向键←→控制方向,蛇头撞在蛇身上或四周墙壁会导致游戏失败。
蛇身控制和碰撞检测
蛇身控制和碰撞检测的逻辑写在SnakeController.cs
文件中。
蛇头运动的思路是将蛇头不断朝forward
的方向前进,前进速度等于速度数值x当前时间。同时通过一个list来记录蛇头运动的历史轨迹,蛇身通过该轨迹进行运动。
为了区分延申出来的蛇身是初始蛇身还是新延申的蛇身,对新延申的蛇身打上Block标签,不进行区分则会导致刚开始碰撞即触发蛇头蛇身碰撞,导致游戏结束。
完整代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SnakeController : MonoBehaviour
{
// 设置
public float moveSpeed = 5f;
public float steerSpeed = 180f;
public float bodySpeed = 5f;
public int Gap = 10;
// 预制体
public GameObject bodyPrefab; //身体组件
// 身体组件集合
private List<GameObject> _bodyParts = new List<GameObject>();
private List<Vector3> _positionHistory = new List<Vector3>();
//音乐控制器
public AudioController audioController;
private void Start()
{
addBodyPart();
audioController = GameObject.FindGameObjectWithTag("Audio").GetComponent<AudioController>();
}
private void Update()
{
// 向前移动
transform.position += transform.forward * moveSpeed * Time.deltaTime;
// 方向操控
float steerDirection = Input.GetAxis("Horizontal"); // 返回值从 -1 到 1
transform.Rotate(Vector3.up * steerDirection * steerSpeed * Time.deltaTime);
// 保存位置移动史
_positionHistory.Insert(0, transform.position);
// 移动身体组件
int index = 0;
foreach (var body in _bodyParts)
{
Vector3 point = _positionHistory[Mathf.Clamp(index * Gap, 0, _positionHistory.Count - 1)];
// 让贪吃蛇的身体组件沿着头部的移动轨迹运动
Vector3 moveDirection = point - body.transform.position;
body.transform.position += moveDirection * bodySpeed * Time.deltaTime;
// 让身体组件朝向头部移动的方向
body.transform.LookAt(point);
index++;
}
}
// 蛇身延长
private void addBodyPart()
{
GameObject body = Instantiate(bodyPrefab, new Vector3(0, transform.position.y, 0), Quaternion.identity);
_bodyParts.Add(body);
}
// 后续添加的body打上Block标签
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);
}
//触发检测
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Food")
{
//必须先删除,否则会导致多次触发
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);
}
}
}
食物旋转
控制食物旋转比较简单,在update中加入Rotate即可。
Food.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
void Start()
{
}
void Update()
{
//旋转
transform.Rotate(Vector3.up);
}
}
食物随机生成
食物随机生成我并没有采用随机数的方式,三维场景容易出现问题。因此这里在场景中添加了6个食物生成的点位,当食物被触发之后,在随机的一个点位上生成新的食物。
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);
}
}
场景切换
这里对于游戏开始界面和结束界面分别用不同的场景进行隔离,切换时只需一行代码:
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
这里的Index为打包时场景的序号顺序。
本地WebGL测试
使用WebGL打包之后,会得到3个文件夹和一个index.html文件,直接打开index.html会报错,需要使用服务器方式去运行。
首先在win10上配置服务器相关组件
之后在打包的文件夹下新建一个文件web.config
,输入以下内容:
<?xml version="1.0" encoding="utf-8"?>
<!--
有关如何配置 ASP.NET 应用程序的详细信息,请访问
https://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.webServer>
<httpProtocol>
<!-- 允许跨域配置 -->
<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>
之后在iis中,新建一个http服务器,选择一个不被占用的端口,我这里选择8080端口。
开启网站后,在浏览器输入http://localhost:8080/
,即可访问测试。
Github部署
Github部署非常容易,新建一个仓库,将打包出的内容直接上传。
然后在Settings/Pages选择main分支,点击Save,过几分钟就会在上方出现访问网址。
遗留问题:打包前后测试不一致
目前该项目在untiy运行测试时正常, 但打包出webgl或exe时,却出现蛇身分离的情况,看了一些打包时的选项,仍未解决该问题,有了解这一问题的读者欢迎在评论区交流。