Unity3D初级实战项目之方块跑酷

目录

  • 初始化项目开发环境
    • 初始化项目
    • 屏幕自适应
  • 游戏UI界面元素布局
    • 开始界面UI
    • 角色选择(商城)界面UI
    • 游戏界面UI
  • 地图生成算法之菱形布局
    • Resources资源加载
    • 代码生成地图菱形布局
  • 地图生成算法之墙壁边界
    • 菱形地图双排布局
    • 地图瓷砖颜色美化
    • 墙壁边界生成
  • 地图生成算法之数据管理
    • 美化瓷砖地图
    • 地图数据存储
    • 地图数据存储测试
  • 角色出生与基本移动控制
    • 角色出生
    • 地图信息展示
    • 角色基本移动
  • 蜗牛痕迹与移动边界控制
    • 蜗牛痕迹
    • 移动边界控制
  • 地图生成算法之连续生成
    • 地图连续生成
  • 摄像机跟随角色移动
    • 摄像机跟随
  • 地图生成算法之地面塌陷
    • 摄像机自动跟随
    • 地面塌陷
  • 地图生成算法之路障生成
    • 何为概率
    • 路障生成概率
  • 使用协程生成路障移动动画
    • 路障生成优化
    • 路障动画
  • 角色与路障交互死亡判断
    • 路障蜗牛痕迹
    • 死亡判断
  • 游戏奖励物品生成与交互
    • 奖励物体生成
    • 触发吃掉奖励物品
  • PlayerPrefs存储游戏分数信息
    • PlayerPrefs
    • 游戏分数信息存储
  • 游戏UI界面逻辑【上】
    • UI界面逻辑
  • 游戏UI界面逻辑【下】
  • Android触屏操作与APK细节配置
    • 触屏操作
    • APK细节配置

初始化项目开发环境

初始化项目

1、创建项目
在这里插入图片描述
添加如下文件夹
在这里插入图片描述
2、NGUI导入(示例工程不要导入)
将下载好的NGUI Next-Gen UI拖入Assets,并将如图所示取消
在这里插入图片描述
3、相关资源导入
①将所有图片资源拖入UI中
②将模型资源拖入Assets
③将字体文件拖入Fonts
保存当前场景为01
在这里插入图片描述
4、Atlas图集资源制作
①打开Atlas Maker
在这里插入图片描述
②选中UI中的所有图片资源
在这里插入图片描述
③点击Create
在这里插入图片描述

屏幕自适应

1、什么是屏幕自适应?
1、随着Android类手机的不断发展,各种各样的Android手机厂商推出了各
种各样的Android手机。随着硬件的不断升级,手机屏幕的尺寸越来越大,屏
幕分辨率也变的千奇百怪。

2、款商业游戏正式上线前都会进行几百台手机测试,其中主要测试的是就是屏幕自适应,以及是否有兼容性的Bug。

3、屏幕自适应:主要是游戏的UI界面在不同尺寸,不同屏幕比例的手机上都能在正常的位置显示。
2、显示分辨率以及比例
本套课程以16:9的分辨率进行自适应处理。

比例值是:1920/1080=1.777

19201080
1280
720
960540
720
405
640360
320
180
3、NGUI初步实现UI自适应
Game面板创建显示用的分辨率尺寸
将如下四种尺寸添加:
19201080
1280
720
960540
720
405
在这里插入图片描述
4、UIRoot组件相关设置:

1>缩放类型:Constrained On Mobiles

2>内容宽度/高度:填写数值,勾选Fit

游戏UI界面元素布局

开始界面UI

游戏开始界面,主要功能用于开始游戏,选择角色,展示积分以及游戏功能设置。
当前界面的根名为:Start_Ul。
创建空物体,并命名Start_UI
在这里插入图片描述
添加图片logo,点击Snap,并选择Based On Width,并调整合适大小位置,将该图片更名为logo.
在这里插入图片描述
将其他图片如图摆放:
在这里插入图片描述
NGUI中添加label标签:
在这里插入图片描述
继续利用label标签和Sprite标签做出如下图所示:
在这里插入图片描述

角色选择(商城)界面UI

角色选择界面有两个功能:

功能一,展示游戏所有的角色;功能二,使用金币购买角色。

当前界面的根名为:Shop_UI
由于开始界面和角色选择界面有相同之处,可以将Start_UI复制粘贴并更名为Shop_UI,在此基础上进行修改。
如图:
在这里插入图片描述
将play图片失活,添加另一种play图片制作如下:
在这里插入图片描述
其中文本100可以作为该图片的子对象存在。

游戏界面UI

游戏界面,游戏的主要操作界面,可以暂停游戏,实时显示当前得分。

当前界面的根名为:Game_UI
在这里插入图片描述

地图生成算法之菱形布局

Resources资源加载

1.动态加载资源
目的是替代Inspector面板拖拽赋值。
因为拖拽赋值耦合性太高,且如果资源过多时,拖拽赋值很容易出错,而且还不
容易管理。
2.Resources 动态加载资源
1新建资源目录“Resources”存放预制体文件;
2使用Resources.Load(“资源名”)进行动态资源加载;

代码生成地图菱形布局

1.普通矩形平铺生成
底板瓷砖模型的宽度是:0.254f;
两层For循环嵌套生成长方形的地面效果。
Quaternion.Euler(Vector3)可以将三维向量转换为四元数。
①创建Resources文件夹
②将tile_white预设体拖入Resources文件夹
③创建空物体,并命名为:MapManager,并创建相应脚本。
④使用Resources动态资源加载tile_white
在这里插入图片描述
⑤查找MapManager对象

private Transform m_Transform;
void Start()
{
    m_Transform = gameObject.GetComponent<Transform>();
}

⑥将创建的每一个tile的父对象设置为MapManager

private void CreateMapItem()
{
    for(int i = 0;i < 10;i++)
    {
        for(int j = 0;j < 5;j++)
        {
            GameObject tile = GameObject.Instantiate(m_prefab_tile,Vector3.zero, Quaternion.identity);
            tile.GetComponent<Transform>().SetParent(m_Transform);
        }
    }
}

⑦设置瓷砖位置
由于底板瓷砖模型的宽度是:0.254f

Vector3 pos = new Vector3(j * 0.254f, 0, i * 0.254f);

⑧设置瓷砖角度

Vector3 rot = new Vector3(-90, 0, 0);

⑨设置主摄像机
size:1.4
Projection:Orthographic
Far:15
并设置到合适位置
在这里插入图片描述
2.等腰直角三角形求底边实现单排菱形平铺

等腰直角三角形求底边:2的平方根 乘以 直角边长;
Unity3D代码实现: M a t h f . S q r t ( 2 ) ∗ n ; Mathf.Sqrt(2)*n; Mathf.Sqrt(2)n;
①旋转角度

Vector3 rot = new Vector3(-90, 45, 0);

②调整位置

Vector3 pos = new Vector3(j * Mathf.Sqrt(2) * 0.254f, 0, i * Mathf.Sqrt(2) * 0.254f);

③调整摄像机
在这里插入图片描述
优化:

private float bottomLength = Mathf.Sqrt(2) * 0.254f;
Vector3 pos = new Vector3(j * bottomLength, 0, i * bottomLength);

地图生成算法之墙壁边界

菱形地图双排布局

第二排菱形位置需要偏移,偏移量为半个底边长度。

for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 5; j++)
    {
        Vector3 pos = new Vector3(j * bottomLength + bottomLength / 2, 0, i * bottomLength + bottomLength / 2);
        Vector3 rot = new Vector3(-90, 45, 0);
        GameObject tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
        tile.GetComponent<Transform>().SetParent(m_Transform);
    }
}

地图瓷砖颜色美化

1.颜色数值转换

Unity的Inspector 面板上的颜色R,G,B都是0~255表示;
但是我们在代码中表示颜色的R,G,B使用的是0~1;
但是我们日常工作中通用的是0~255的表示方法,但是为了在代码中使用,就
需要进行数值转换。
转换公式:颜色值/255f= 小数值;
①更改颜色值
找到每一个瓷砖的MeshRenderer组件中的material中的color进行更改

tile.GetComponent<MeshRenderer>().material.color = Color.blue;
tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = Color.blue;

②设置自定义颜色
由于纯色可能会影响美观,我们将颜色设置为自定义颜色

private Color colorOne = new Color(124 / 255f, 155 / 255f, 230 / 255f);

③修改瓷砖的光滑度
由于瓷砖表面看起来会反光,在瓷砖预设体中的Shader中修改Smoothness为0
在这里插入图片描述
④同理将双排数瓷砖颜色进行设置

private Color colorTwo = new Color(125 / 255f, 169 / 255f, 233 / 255f);

在这里插入图片描述

墙壁边界生成

奇数行的头和尾两块瓷砖使用墙壁模型生成
①将预设体中的wall2拖入Resources
②动态加载wall2资源

m_prefab_wall = Resources.Load("wall2") as GameObject;

③添加瓷砖
对于单数排瓷砖,可以在瓷砖开头和结尾添加墙壁,中间则添加瓷砖

if(j == 0 || j == 4)
{
    tile = GameObject.Instantiate(m_prefab_wall, pos, Quaternion.Euler(rot));
}
else
{
    tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
}

若此时运行则会发生空指针引用异常,由于此句代码会寻找瓷砖的子物体,但墙壁预设体没有子物体,所以报错

tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = colorOne;

解决办法:将该代码与生成瓷砖时同时发生。
在这里插入图片描述
④调整地图宽度
将代码中j的最大长度加一个。

地图生成算法之数据管理

美化瓷砖地图

1、摄像机拍摄角度和位置调整
在这里插入图片描述
2、灯光位置与角度以及颜色调整
在这里插入图片描述
3、墙壁颜色调整

private Color colorWall = new Color(87 / 255f, 93 / 255f, 169 / 255f);

在这里插入图片描述

地图数据存储

使用List集合加数组的形式,将地图存储起来。
横向存储进数组,纵向存储进List集合。
①引入命名空间

using System.Collections.Concurrent;

②声明数据存储集合

//地图数据存储
private List<GameObject[]> mapList = new List<GameObject[]>();

③修改地图生成代码
使得地图一排一排生成,而不是先生成单数排,再生成双数排

for(int i = 0;i < 10;i++)
{
    for(int j = 0;j < 6;j++)
    {
        Vector3 pos = new Vector3(j * bottomLength, 0, i * bottomLength);
        Vector3 rot = new Vector3(-90, 45, 0);
        GameObject tile = null;
        if(j == 0 || j == 5)
        {
            tile = GameObject.Instantiate(m_prefab_wall, pos, Quaternion.Euler(rot));
            tile.GetComponent<MeshRenderer>().material.color = colorWall;
        }
        else
        {
            tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
            tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = colorOne;
            tile.GetComponent<MeshRenderer>().material.color = colorOne;
        } 
        tile.GetComponent<Transform>().SetParent(m_Transform);
    }
    for (int j = 0; j < 5; j++)
    {
        Vector3 pos = new Vector3(j * bottomLength + bottomLength / 2, 0, i * bottomLength + bottomLength / 2);
        Vector3 rot = new Vector3(-90, 45, 0);
        GameObject tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
        tile.GetComponent<MeshRenderer>().material.color = colorTwo;
        tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = colorTwo;
        tile.GetComponent<Transform>().SetParent(m_Transform);
    }
}

④数据存储
在每一排地图生成前,声明一个临时数组,用来存储每一排的瓷砖

GameObject[] item = new GameObject[6];

每次瓷砖生成时,将瓷砖加入临时数组

item[j] = tile;

当一排瓷砖生成结束之后,将该临时数组加入MapList中

mapList.Add(item);

地图数据存储测试

void Update()
{
    if(Input.GetKeyDown(KeyCode.Space))
    {
        string str = "";
        for (int i = 0; i < mapList.Count; i++)
        {
            for(int j = 0;j < mapList[i].Length; j++)
            {
                str += mapList[i][j].name;
            }
            str += "\n";
        }
        Debug.Log(str);
    }
}

角色出生与基本移动控制

角色出生

读取地图集合中的数据,指定主角模型的出生点和旋转状态
①确定角色模型cube_car
②创建PlayerController脚本,将其拖到该预设体上
③找到该预设体的Transform组件,MapManager脚本

private Transform m_Transform;
private MapManager m_MapManager;
void Start()
{
    m_Transform = gameObject.GetComponent<Transform>();
    m_MapManager = GameObject.Find("MapManager").GetComponent<MapManager>();
}

为了便于访问MapManager中的MapList,将private修改为public
按下M键角色出生:

void Update()
{
    if(Input.GetKeyDown(KeyCode.M))
    {
        m_Transform.position = m_MapManager.mapList[3][2].GetComponent<Transform>().position + new Vector3(0,0.254f / 2,0);
        m_Transform.rotation = m_MapManager.mapList[3][2].GetComponent<Transform>().rotation;
    }
}

优化代码:

private int z = 3;
private int x = 2;
Transform PlayerPos = m_MapManager.mapList[z][x].GetComponent<Transform>();
m_Transform.position = PlayerPos.position + new Vector3(0,0.254f / 2,0);
m_Transform.rotation = PlayerPos.rotation;

地图信息展示

在地图模型上显示数据集合的信息
①将瓷砖和墙壁的icon改为绿色
在这里插入图片描述
②当按下空格打印地图数据时添加如下代码

mapList[i][j].name = i + "--" + j;

在这里插入图片描述

角色基本移动

使用A和D进行按键控制。读取地图中的位置信息,进行角色移动效果模拟。
①将按下M键的逻辑封装为一个方法

void SetPlayerPos()
{
    Transform PlayerPos = m_MapManager.mapList[z][x].GetComponent<Transform>();
    m_Transform.position = PlayerPos.position + new Vector3(0, 0.254f / 2, 0);
    m_Transform.rotation = PlayerPos.rotation;
}

②修改Update()逻辑

void Update()
{
    if(Input.GetKeyDown(KeyCode.M))
    {
        SetPlayerPos();
    }
    if(Input.GetKeyDown(KeyCode.A))
    {
        z++;
        if(z % 2 == 1)
        {
            x--;
        }
        SetPlayerPos();
    }
    if(Input.GetKeyDown(KeyCode.D))
    {
        z++;
        if(z % 2 == 0)
        {
            x++;
        }
        SetPlayerPos();
    }
}

蜗牛痕迹与移动边界控制

蜗牛痕迹

角色经过的瓷砖要改变颜色,类似于蜗牛经过的痕迹
①角色控制中声明两种颜色

private Color colorOne = new Color(122 / 255f, 85 / 255f, 179 / 255f);
private Color colorTwo = new Color(126 / 255f, 93 / 255f, 183 / 255f);

②在SetPlayerPos中对经过瓷砖进行渲染

void SetPlayerPos()
{
    Transform PlayerPos = m_MapManager.mapList[z][x].GetComponent<Transform>();
    MeshRenderer normal_a2 = PlayerPos.Find("normal_a2").GetComponent<MeshRenderer>();
    if(z % 2 == 0)
    {
        normal_a2.material.color = colorOne;
    }
    else
    {
        normal_a2.material.color = colorTwo;
    }
    m_Transform.position = PlayerPos.position + new Vector3(0, 0.254f / 2, 0);
    m_Transform.rotation = PlayerPos.rotation;
}

移动边界控制

角色移动到左右边界时,就不可再移动。
角色移动到上方边界时,地图就需要继续生成。
①修改右边界
观察地图上的下标,发现当x = 0时,z不能再加一,且x = 0时,x不能再减一。即当按下A键的控制逻辑修改为:

if(Input.GetKeyDown(KeyCode.A))
{
    if(x != 0)
    {
        z++;
    }
    if(z % 2 == 1 && x != 0)
    {
        x--;
    }
    SetPlayerPos();
}

②修改左边界
当按下D键控制逻辑修改为:

if(Input.GetKeyDown(KeyCode.D))
{
    if(x != 4 || z % 2 == 0)
    {
        z++;
    }
    if(z % 2 == 0 && x != 4)
    {
        x++;
    }
    SetPlayerPos();
}

地图生成算法之连续生成

地图连续生成

当我们的角色快接近地图前方边缘的时候,需要动态的继续生成地图。
①优化代码:
对于向左向右移动逻辑单独封装为一个函数PlayerControl()

private void PlayerControl()
{
    if (Input.GetKeyDown(KeyCode.A))
    {
        if (x != 0)
        {
            z++;
        }
        if (z % 2 == 1 && x != 0)
        {
            x--;
        }
        SetPlayerPos();
    }
    if (Input.GetKeyDown(KeyCode.D))
    {
        if (x != 4 || z % 2 == 0)
        {
            z++;
        }
        if (z % 2 == 0 && x != 4)
        {
            x++;
        }
        SetPlayerPos();
    }
}

②将CreateMapItem()的访问修饰符改为public
③由于每次生成地图都会在0这个位置生成,因此需要加一个偏移值,将CreateMapItem()添加参数Float offsetZ。
④创建计算角色位置函数CalcPosition()

private void CalcPosition()
{
    if(m_MapManager.mapList.Count - z <= 5)
    {
        float offsetZ = m_MapManager.mapList[m_MapManager.mapList.Count - 1][0].GetComponent<Transform>().position.z + m_MapManager.bottomLength / 2;
        m_MapManager.CreateMapItem(offsetZ);
    }
}

摄像机跟随角色移动

摄像机跟随

在我们的角色移动的时候,摄像机要跟随角色移动。
使用插值运算函数,让摄像机平滑移动:

Vecotr3.Lerp ( Vector3 from, Vector3 to, float t) ;

①创建脚本CameraFollow,并拖到MainCamera
②定义m_Transform并获取组件,即摄像机位置
③定义m_Palyer并获取组件,即玩家位置

m_Player = GameObject.Find("cube_dice").GetComponent<Transform>();

④定义bool型变量,用来控制何时摄像机跟随
⑤定义CameraMove方法
根据主摄像机相对玩家位置找到偏移值,由于摄像机是相对玩家z轴偏移2.08.
由于不希望摄像机左右移动,因此将x轴向位置更新为摄像机x轴向位置

void CameraMove()
{
    if(StartFollow)
    {
        Vector3 nextPos = new Vector3(m_Transfrom.position.x, m_Player.position.y + 2.08f, m_Player.position.z);
        m_Transfrom.position = nextPos;
    }
}

⑥使用插值运算将摄像机平滑移动

m_Transfrom.position = Vector3.Lerp(m_Transfrom.position, nextPos, Time.deltaTime);

地图生成算法之地面塌陷

摄像机自动跟随

当我们按下M键初始化完毕角色位置后,摄像机就开始自动跟随移动。
①在角色控制脚本中,定义m_CameraFollow,并获取

m_CameraFollow = GameObject.Find("Main Camera").GetComponent<CameraFollow>();

②当按下M键时,将StartFollow初始化为true,此时摄像机将自动跟随

地面塌陷

在我们的角色向前移动的同时,后面的地面要不断的塌陷掉落,增加紧迫感。
1>要让地面一排一排的掉落
对每一排瓷砖添加刚体组件使其掉落。
使用协程固定时间间隔,实现地面塌陷。
①定义协程函数TileDown()

private IEnumerator TileDown()
{
    while (true)
    {
        yield return new WaitForSeconds(0.5f);
        for(int i = 0;i < mapList[index].Length;i++)
        {
            mapList[index][i].AddComponent<Rigidbody>();
        }
        index++;
    }
}

②将开启协程函数封装

public void StartTileDown()
{
    StartCoroutine(TileDown());
}

③将关闭协程函数封装

public void StopTileDown()
{
    StopCoroutine(TileDown());
}

④当按下M键时调用开始塌陷函数

2>掉落的地面元素要销毁
定时销毁游戏物体。
①添加刚体之后,瓷砖要销毁

GameObject.Destroy(mapList[index][i],1.0f);

3>给掉落的地面元素添加旋转力

rigidbody.angularVelocity:角速度向量。
将添加刚体时的代码修改为:

Rigidbody rb = mapList[index][i].AddComponent<Rigidbody>();
rb.angularVelocity = new Vector3(Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f)) * Random.Range(1,10);

方向和力随机。
4>停止地面塌陷(游戏结束)

当塌陷到的位置和角色的位置重合,即停止塌陷。
①在MapManager中声明并获取m_PlayerControlloer,以便访问玩家坐标

m_PlayerController = GameObject.Find("cube_dice").GetComponent<PlayerController>();

②将PlayerController中z和x成员变量访问修饰符改为public
③如果横坐标相同,则停止塌陷

if(m_PlayerController.z == index)
{
    StopTileDown();
}

地图生成算法之路障生成

何为概率

在我们的游戏地图中,墙壁是需要固定生成的。
而瓷砖地面有的地方是需要生成坑洞的,有的地方是需要生成陷阱的,有的地方
是需要生成奖励物品的,这些游戏地图中非固定物体(陷阱,奖励)的生成是有
概率的,也就是说是随机的,这样游戏才更有可玩性。

路障生成概率

游戏地图中的“路障”分三种:
1坑洞 2地面陷阱 3天空陷阱

我们的地图,第一段是不生成“路障”的,从第二段开始,即生成路障。

1>概率算法
定义概率值,比如说概率为5%,我们就使用随机数在0~100之间随机,如果
随机得到的数小于或等于5(0~5之间),就说明满足生成路障的概率。
2>坑洞概率生成:

概率基数:0%
路障每生成一段,基数+2%
取值范围:1~30
①定义坑洞生成概率变量
②定义计算概率函数

/// <summary>
/// 计算概率
/// 0:瓷砖
/// 1:坑洞
/// 2:地面陷阱
/// 3:天空陷阱
/// </summary>
/// <returns></returns>
private int CalcPR()
{
    int pr = Random.Range(1, 100);
    if(pr <= pr_hole)
    {
        return 1;
    }
    return 0;
}

③定义增加概率函数

private void AddPR()
{
    pr_hole += 2;
}

④当生成新的一段路面时,增加概率
在PlayerController中的CalcPosition()函数中,满足条件时生成路面。
在这里插入图片描述
⑤生成瓷砖时根据概率选择是生成瓷砖还是坑洞

int pr = CalcPR();
if(pr == 0)
{
    tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
    tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = colorOne;
    tile.GetComponent<MeshRenderer>().material.color = colorOne;
}
else if (pr == 1)
{
    tile = new GameObject();
    tile.GetComponent<Transform>().position = pos;
    tile.GetComponent<Transform>().rotation = Quaternion.Euler(rot);
}

3>地面陷阱概率生成:

概率基数:0%
路障每生成一段,基数+1%
取值范围:31~60
①定义m_prefab_spikes(GameObject),并动态加载
②定义pr_spikes(int)
③修改增加概率函数

public void AddPR()
{
    pr_hole += 2;
    pr_spikes += 1;
}

④修改计算概率函数

private int CalcPR()
{
    int pr = Random.Range(1, 100);
    if(pr <= pr_hole)
    {
        return 1;
    }
    else if(pr > 31 && pr < pr_spikes + 30 )
    {
        return 2;
    }
    return 0;
}

⑤地面生成时,添加判断条件

else if(pr == 2)
{
    tile = GameObject.Instantiate(m_prefab_spikes, pos, Quaternion.Euler(rot));
}

4>天空陷阱概率生成:

概率基数:0%
路障每生成一段,基数+1%
取值范围:61~90
①定义pr_sky_spikes

private int pr_sky_spikes = 0;

②定义m_prefab_sky_spikes,并动态资源加载

m_prefab_sky_spikes = Resources.Load("smashing_spikes") as GameObject;

使用协程生成路障移动动画

路障生成优化

生成地图片段的方法,第二个for循环也要进行“概率”判断。

for (int j = 0; j < 5; j++)
{
    Vector3 pos = new Vector3(j * bottomLength + bottomLength / 2, 0, offsetZ + i * bottomLength + bottomLength / 2);
    Vector3 rot = new Vector3(-90, 45, 0);
    GameObject tile = null;

    int pr = CalcPR();
    if (pr == 0)
    {
        tile = GameObject.Instantiate(m_prefab_tile, pos, Quaternion.Euler(rot));
        tile.GetComponent<Transform>().Find("normal_a2").GetComponent<MeshRenderer>().material.color = colorTwo;
        tile.GetComponent<MeshRenderer>().material.color = colorTwo;
    }
    else if (pr == 1)
    {
        tile = new GameObject();
        tile.GetComponent<Transform>().position = pos;
        tile.GetComponent<Transform>().rotation = Quaternion.Euler(rot);
    }
    else if (pr == 2)
    {
        tile = GameObject.Instantiate(m_prefab_spikes, pos, Quaternion.Euler(rot));
    }
    else if (pr == 3)
    {
        tile = GameObject.Instantiate(m_prefab_sky_spikes, pos, Quaternion.Euler(rot));
    }
    tile.GetComponent<Transform>().SetParent(m_Transform);
    item2[j] = tile;
}

路障动画

使用协程实现路障动画效果。
路障本身是两个模型,我们要控制子模型进行上下位移。

1>地面陷阱动画

上移:0.15f
动作步长:Time.deltaTime*10
①创建脚本Spikes,并拖入路障的预设体
②定义m_Transform并获取
③定义son_Transform并获取
④定义Vector3类型变量normalPos和targetPos变量,并赋值

normalPos = son_Transform.position;
targetPos = son_Transform.position + new Vector3(0, 0.15f, 0);

⑤定义两个协程函数Up(),Down()

private IEnumerator Up()
{
    while(true)
    {
        son_Transform.position = Vector3.Lerp(son_Transform.position, targetPos, Time.deltaTime * 10);
        yield return null;
    }
}
private IEnumerator Down()
{
    while(true)
    {
        son_Transform.position = Vector3.Lerp(son_Transform.position, normalPos, Time.deltaTime * 10);
        yield return null;
    }
}
private IEnumerator UpAndDown()
{
    while (true)
    {
        StopCoroutine("Down");
        StartCoroutine("Up");
        yield return new WaitForSeconds(2.0f);
        StopCoroutine("Up");
        StartCoroutine("Down");
        yield return new WaitForSeconds(2.0f);
    }
}

⑥在Start函数中开启UpAndDown协程
2>天空陷阱动画

下移:0.6fI
动作步长:Time.deltaTime*20
创建脚本SkySpikes,并将Spikes脚本代码复制,将名字修改为天空陷阱的相关名字。

角色与路障交互死亡判断

路障蜗牛痕迹

当路障处于“安全状态”时,角色移动到路障上形成蜗牛痕迹。
操作步骤:
1创建三个tag标签,分别设置给路障和瓷砖模型;
2根据tag判断出“位置类型”,然后查找相应的地面模型进行蜗牛痕迹生成。
①添加三个tag,并且分给三个物体:
在这里插入图片描述
②修改SetPlayerPos():

void SetPlayerPos()
{
    Transform PlayerPos = m_MapManager.mapList[z][x].GetComponent<Transform>();
    MeshRenderer normal_a2 = null;

    if(PlayerPos.tag == "Tile")
    {
        normal_a2 = PlayerPos.Find("normal_a2").GetComponent<MeshRenderer>();
    }
    else if(PlayerPos.tag == "Spikes")
    {
        normal_a2 = PlayerPos.Find("moving_spikes_a2").GetComponent<MeshRenderer>();
    }
    else if(PlayerPos.tag == "Sky_Spikes")
    {
        normal_a2 = PlayerPos.Find("smashing_spikes_a2").GetComponent<MeshRenderer>();
    }
    
    if(z % 2 == 0)
    {
        normal_a2.material.color = colorOne;
    }
    else
    {
        normal_a2.material.color = colorTwo;
    }
    m_Transform.position = PlayerPos.position + new Vector3(0, 0.254f / 2, 0);
    m_Transform.rotation = PlayerPos.rotation;
}

死亡判断

何时死亡?

1被地面路障刺到;(触发判断)
2被天空路障刺到;(触发判断)
①定义bool变量life设置为true
②如果life为true才可以调用PlayerControl,即控制玩家
③添加函数GameOver():

private void GameOver()
{
    life = false;
}

③为玩家添加盒状碰撞器,并勾选触发器选项
在这里插入图片描述
④为地面陷阱的子物体尖刺添加盒状碰撞器勾选触发器选项,同时添加Spikes_Attack的tag。并添加刚体组件,取消勾选使用重力。
对天空陷阱做相同操作

3掉入坑洞陷阱;(刚体下落)
①修改SetPlayerPos函数:

void SetPlayerPos()
{
    Transform PlayerPos = m_MapManager.mapList[z][x].GetComponent<Transform>();
    MeshRenderer normal_a2 = null;
    m_Transform.position = PlayerPos.position + new Vector3(0, 0.254f / 2, 0);
    m_Transform.rotation = PlayerPos.rotation;
    if (PlayerPos.tag == "Tile")
    {
        normal_a2 = PlayerPos.Find("normal_a2").GetComponent<MeshRenderer>();
    }
    else if(PlayerPos.tag == "Spikes")
    {
        normal_a2 = PlayerPos.Find("moving_spikes_a2").GetComponent<MeshRenderer>();
    }
    else if(PlayerPos.tag == "Sky_Spikes")
    {
        normal_a2 = PlayerPos.Find("smashing_spikes_a2").GetComponent<MeshRenderer>();
    }
    
    if(normal_a2 != null)
    {
        if (z % 2 == 0)
        {
            normal_a2.material.color = colorOne;
        }
        else
        {
            normal_a2.material.color = colorTwo;
        }
    }
    else
    {
        gameObject.AddComponent<Rigidbody>();
        StartCoroutine("GameOver", true);
    }
}

②将GameOver函数修改为协程函数:

private IEnumerator GameOver(bool b)
{
    if(b)
    {
        yield return new WaitForSeconds(0.5f);
    }
    life = false;
    Time.timeScale = 0;
}

4自己所在位置地面塌陷。(刚体下落)
地面塌陷时的代码添加如下代码:
在这里插入图片描述

游戏奖励物品生成与交互

优化代码:
修改PlayerController中的GameOver:

public IEnumerator GameOver(bool b)
{
    if(b)
    {
        yield return new WaitForSeconds(0.5f);
    }
    if(life)
    {
        life = false;
        m_CameraFollow.StartFollow = false;
    }
}

奖励物体生成

奖励物品要在正常的瓷砖物体的上方生成,奖励物体的生成概率是一个固定值。
奖励物品自身要进行轴向的旋转。
奖励物品生成的位置在瓷砖上方0.06f。
①定义m_prefab_gem,并动态加载
②定义宝石的生成概率

private int pr_gem = 2;

③添加计算宝石生成概率的函数

/// <summary>
/// 计算宝石生成概率
/// </summary>
/// <returns>0:不生成,1:生成</returns>
private int CalcGemPR()
{
    int pr = Random.Range(1, 100);
    if(pr <= pr_gem)
    {
        return 1;
    }
    return 0;
}

④当生成瓷砖时有概率生成宝石

int gemPR = CalcGemPR();
if(gemPR == 1)
{
    GameObject gem = GameObject.Instantiate(m_prefab_gem,tile.GetComponent<Transform>().position + new Vector3(0,0.06f,0),Quaternion.identity) as GameObject;
    gem.GetComponent<Transform>().SetParent(tile.GetComponent<Transform>());
}

⑤创建Gem脚本,获取Transform组件。定义m_gem变量,获取宝石的子物体gem 3;
⑥旋转奖励物品

m_gem.Rotate(Vector3.up);

触发吃掉奖励物品

角色移动,与奖励物品接触,销毁奖励物体,然后增加角色奖励物品得分。
①定义gemCount变量,定义AddGemCount函数

private void AddGemCount()
{
    gemCount++;

}

②对于gem预设体的子物体gem 3:添加盒状碰撞器勾选触发器,添加刚体组件,取消勾选使用重力,添加Gem的tag.
③修改onTriggerEnter函数

private void OnTriggerEnter(Collider coll)
{
    if(coll.tag == "Spikes_Attack")
    {
        StartCoroutine("GameOver", false);
    }
    if(coll.tag == "Gem")
    {
        GameObject.Destroy(coll.gameObject.GetComponent<Transform>().parent.gameObject);
        AddGemCount();
    }
}

实现移动得分记录。
①定义scoreCount,并定义分数增加函数

private void AddScoreCount()
{
    scoreCount++;
}

②当z++时,调用上述函数

PlayerPrefs存储游戏分数信息

PlayerPrefs

PlayerPrefs类是Unity提供的一个用于存储少量游戏数据的工具类,数据是以“键值对”的形式存储的。

数据在不同的平台存储的位置是不同的。
在windows 平台PlayerPrefs的数据是存储在注册表中的。

游戏分数信息存储

PlayerPrefs 存储位置
1win+R调出运行面板,输入“regedit”打开注册表面板;
2HKEY_CURRENT_USER – >SOFTWARE – >公司名 – >应用名。

①首先做一些设置:File->Build Settings->Player Settings
Company Name:MKCOOE
Product Name:CubeRun
宝石数存储
在游戏开始时,将宝石数读取出来,游戏结束时,再将新的宝石数存储回去。
②在PlayerController中的start中添加代码:

gemCount = PlayerPrefs.GetInt("gem", 0);

③定义存储数据函数:

private void SaveData()
{
    PlayerPrefs.SetInt("Gem", gemCount);
}

④调用该函数:
当游戏结束时调用。

分数(最高分)存储
游戏结束时,将当前的得分与存储中的分数进行比较,如果高于原来的得分,就将新的晶高分存储回去。

⑤修改SaveData函数

private void SaveData()
{
    PlayerPrefs.SetInt("gem", gemCount);
    if(scoreCount > PlayerPrefs.GetInt("score",0))
    {
        PlayerPrefs.SetInt("score", scoreCount);
    }
}

游戏UI界面逻辑【上】

UI界面逻辑

①创建UIManager脚本将该脚本拖到UI Root,删除shop_UI(先不做该UI)
1>开始与游戏界面隐藏与显示切换;
②声明并获取,m_StartUI和m_GameUi,将两种UI手动激活,将m_GameUI用代码失活:

m_StartUI = GameObject.Find("Start_UI");
m_GameUI = GameObject.Find("Game_UI");
m_GameUI.SetActive(false);

2>开始界面显示宝石数以及最高分数;
①将此图所在标签更名为Score_Label
在这里插入图片描述
②将此图所在标签更名为Gem_Label
在这里插入图片描述③定义m_ScoreLabel和m_GemLabel并获取

m_ScoreLabel = GameObject.Find("Score_Label").GetComponent<UILabel>();
m_GemLabel = GameObject.Find("Gem_Label").GetComponent<UILabel>();

④定义初始化函数:

private void Init()
{
    m_ScoreLabel.text = PlayerPrefs.GetInt("score", 0) + "";
    m_GemLabel.text = PlayerPrefs.GetInt("gem", 0) + "/100";
}

3>游戏界面初始化宝石数与分数;
①更名
在这里插入图片描述
在这里插入图片描述
②定义获取

private UILabel m_GameScoreLabel;
private UILabel m_GameGemLabel;
m_GameScoreLabel = GameObject.Find("GameScoreLabel").GetComponent<UILabel>();
m_GameGemLabel = GameObject.Find("GameGemLabel").GetComponent<UILabel>();

③修改初始化函数

private void Init()
{
    m_ScoreLabel.text = PlayerPrefs.GetInt("score", 0) + "";
    m_GemLabel.text = PlayerPrefs.GetInt("gem", 0) + "/100";

    m_GameScoreLabel.text = "0";
    m_GameGemLabel.text = PlayerPrefs.GetInt("gem", 0) + "/100";
}

4>使用委托给开始按钮绑定点击事件处理方法;
①添加盒状碰撞器,并且附加Button Script
在这里插入图片描述
②定义并获取

private GameObject m_PlayButton;
m_PlayButton = GameObject.Find("play_btn");

③添加委托

UIEventListener.Get(m_PlayButton).onClick = PlayButtonClick;
private void PlayButtonClick(GameObject go)
{
    Debug.Log("游戏开始");
}

④修改PlayButtonClick:

    private void PlayButtonClick(GameObject go)
    {
        Debug.Log("游戏开始");
        m_StartUI.SetActive(false);
        m_GameUI.SetActive(true);
    }
}

⑤将PlayerController脚本中做如下修改:
在这里插入图片描述
⑥定义并获取:

m_PlayerController = GameObject.Find("cube_dice").GetComponent<PlayerController>();

⑦修改PlayerButtonClick:

private void PlayButtonClick(GameObject go)
{
    Debug.Log("游戏开始");
    m_StartUI.SetActive(false);
    m_GameUI.SetActive(true);
    m_PlayerController.GameStart();
}

5>游戏界面更新分数UI和宝石数UI;
①在UIManager脚本中添加函数:

public void UpdateData(int score,int gem)
{
    m_GameScoreLabel.text = score.ToString();
    m_GameGemLabel.text = gem + "/100";
}

②定义获取

m_UIManager = GameObject.Find("UI Root").GetComponent<UIManager>();

③调用函数

private void AddGemCount()
{
    gemCount++;
    m_UIManager.UpdateData(scoreCount, gemCount);
}
private void AddScoreCount()
{
    scoreCount++;
    m_UIManager.UpdateData(scoreCount, gemCount);
    Debug.Log("分数:" + scoreCount);
}

游戏UI界面逻辑【下】

1>角色死亡后等待2秒然后UI界面跳转回开始界面;
①在UIManager中添加函数

public void ResetUI()
{
    m_StartUI.SetActive(true);
    m_GameUI.SetActive(false);
}

②在PlayerController中添加协程:

private IEnumerator ResetGame()
{
    yield return new WaitForSeconds(2);
    m_UIManager.ResetUI();
}

③生命结束时,开启协程
在这里插入图片描述

2>角色死亡后更新开始界面的分数;
修改UIManager:UpdateData函数

public void UpdateData(int score,int gem)
{
    m_GemLabel.text = gem + "/100";
    m_GameScoreLabel.text = score.ToString();
    m_GameGemLabel.text = gem + "/100";
}

3>重置角色:

取消角色的刚体组件;
在PlayerController中添加函数ResetPlayer:

public void UpdateData(int score,int gem)
{
    m_GemLabel.text = gem + "/100";
    m_GameScoreLabel.text = score.ToString();
    m_GameGemLabel.text = gem + "/100";
}

在ResetGame中调用

重置出生点位置;
修改ResetPlayer()

private void ResetPlayer()
{
    GameObject.Destroy(gameObject.GetComponent<Rigidbody>());
    z = 3;
    x = 2;
}

重置 life值;

private void ResetPlayer()
{
    GameObject.Destroy(gameObject.GetComponent<Rigidbody>());
    z = 3;
    x = 2;

    life = true;
}

重置当前分数(UI也要重置);

private void ResetPlayer()
{
    GameObject.Destroy(gameObject.GetComponent<Rigidbody>());
    z = 3;
    x = 2;

    life = true;

    scoreCount = 0;
}
public void ResetUI()
{
    m_StartUI.SetActive(true);
    m_GameUI.SetActive(false);
    m_GameScoreLabel.text = "0";//UI重置
}

4>重置场景:

删掉现有的所有场景元素;
在MapManager脚本中添加函数:

public void ResetGameMap()
{
    Transform[] sonTransform = m_Transform.GetComponentsInChildren<Transform>();
    for(int i = 1;i <  sonTransform.Length;i++)
    {
        GameObject.Destroy(sonTransform[i].gameObject);
    }
}

重置相关的概率值;

public void ResetGameMap()
{
    Transform[] sonTransform = m_Transform.GetComponentsInChildren<Transform>();
    for(int i = 1;i <  sonTransform.Length;i++)
    {
        GameObject.Destroy(sonTransform[i].gameObject);
    }
    pr_hole = 0;
    pr_spikes = 0;
    pr_sky_spikes = 0;
    pr_gem = 2;
}

重置塌陷角标;

public void ResetGameMap()
{
    Transform[] sonTransform = m_Transform.GetComponentsInChildren<Transform>();
    for(int i = 1;i <  sonTransform.Length;i++)
    {
        GameObject.Destroy(sonTransform[i].gameObject);
    }
    pr_hole = 0;
    pr_spikes = 0;
    pr_sky_spikes = 0;
    pr_gem = 2;

    index = 0;
}

清空List集合;

public void ResetGameMap()
{
    Transform[] sonTransform = m_Transform.GetComponentsInChildren<Transform>();
    for(int i = 1;i <  sonTransform.Length;i++)
    {
        GameObject.Destroy(sonTransform[i].gameObject);
    }
    pr_hole = 0;
    pr_spikes = 0;
    pr_sky_spikes = 0;
    pr_gem = 2;

    index = 0;

    mapList.Clear();
}

重新生成新的地图;

public void ResetGameMap()
{
    Transform[] sonTransform = m_Transform.GetComponentsInChildren<Transform>();
    for(int i = 1;i <  sonTransform.Length;i++)
    {
        GameObject.Destroy(sonTransform[i].gameObject);
    }
    pr_hole = 0;
    pr_spikes = 0;
    pr_sky_spikes = 0;
    pr_gem = 2;

    index = 0;

    mapList.Clear();

    CreateMapItem(0);
}

ResetGame中调用该协程

private IEnumerator ResetGame()
{
    yield return new WaitForSeconds(2);
    ResetPlayer();
    m_MapManager.ResetGameMap();
    m_UIManager.ResetUI();
}

5>重置摄像机:
I
重置摄像机位置;
在CameraFollow脚本中
①定义当前位置,并初始当前位置

private Vector3 normalPos;
normalPos = m_Transfrom.position;

②添加函数ResetCamera

public void ResetCamera()
{
    m_Transfrom.position = normalPos;
}

③ResetGame协程中调用:

private IEnumerator ResetGame()
{
    yield return new WaitForSeconds(2);
    ResetPlayer();
    m_MapManager.ResetGameMap();
    m_CameraFollow.ResetCamera();
    m_UIManager.ResetUI();
}

Android触屏操作与APK细节配置

触屏操作

NGUI 中的Button是可以接受屏幕触屏事件的,我们只需要在GameUI 使用

两个“透明按钮”来模拟点击区域就可以实现该功能。

操作步骤:

1GameUI 界面制作两个按钮;
在这里插入图片描述
附加盒状碰撞器和Button脚本
2分离A和D的按键执行代码;
在这里插入图片描述

3两个按钮绑定对应的按键执行代码;
①定义获取:

m_Left = GameObject.Find("Left");
m_Right = GameObject.Find("Right");

②按键绑定

m_Left = GameObject.Find("Left");
UIEventListener.Get(m_Left).onClick = Left;
m_Right = GameObject.Find("Right");
UIEventListener.Get(m_Right).onClick = Right;
private void Left(GameObject go)
{
    m_PlayerController.Left();
}
private void Right(GameObject go)
{
    m_PlayerController.Right();
}

4将两个按钮进行透明化处理。

APK细节配置

1.切换平台为Android平台,并将场景加入打包队列

Unity 会对项目资源进行相应的“格式转换”。
在这里插入图片描述
点击Add Open Scenes,并更改分辨率为480*854

2.设置 Bundle Identifier

APK的唯一标识符,一般是公司域名反写+应用名称。

比如:net.mkcode.monkeycuberun
要想正确打包出APK,电脑上必须安装JDK和SDK,并配置好Unity 对着

两个工具的引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/588932.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git提交错了?别慌,直接删除提交记录

为什么要删除提交历史 前几天产品提了个很扯淡的需求&#xff0c;我在代码了进行了吐槽.... 要命的是我不下心进行了代码提交&#xff1a; 我们的远程仓库大家都能看见的 这要是被其他人发现就惨了&#xff01;当务之急&#xff0c;我必须立刻马上删除这一条提交记录&#xff…

菜鸡学习netty源码(一)——ServerBootStrap启动

1.概述 对于初学者而然,写一个netty本地进行测试的Server端和Client端,我们最先接触到的类就是ServerBootstrap和Bootstrap。这两个类都有一个公共的父类就是AbstractBootstrap. 那既然 ServerBootstrap和Bootstrap都有一个公共的分类,那就证明它们两个肯定有很多公共的职…

EMP.DLL是什么东西?游戏提示EMP.DLL文件缺失怎么解决

emp.dll文件是Windows操作系统中的一种动态链接库文件&#xff0c;它被设计为可以被多个程序共享使用的模块化文件。这种设计旨在提高系统效率&#xff0c;减少内存消耗&#xff0c;并简化软件的维护和更新。DLL文件通常包含了一系列相关的函数和变量&#xff0c;这些函数和变量…

全景剖析阿里云容器网络数据链路(七):Terway DataPath V2(Terway≥1.8.0)

作者&#xff1a;余凯 前言 近几年&#xff0c;企业基础设施云原生化的趋势越来越强烈&#xff0c;从最开始的IaaS化到现在的微服务化&#xff0c;客户的颗粒度精细化和可观测性的需求更加强烈。容器网络为了满足客户更高性能和更高的密度&#xff0c;也一直在高速的发展和演…

qt学习篇---界面按键关联(信号和槽)

目录 1.qt基础 2.做一个界面 创建project UI界面设计 信号和槽 1.控件改名字 2.什么是信号和槽 3.怎么关联信号和槽 自动关联 手动关联 1.qt基础 qt可移植性强&#xff0c;不久会用到MCU。很有意义学习 2.做一个界面 创建project 不要中文路径 选择QWidget .pro文件…

ASP.NET实验室预约系统的设计

摘 要 实验室预约系统的设计主要是基于B/S模型&#xff0c;在Windows系统下&#xff0c;运用ASP.NET平台和SQLServer2000数据库实现实验室预约功能。该设计主要实现了实验室的预约和管理功能。预约功能包括老师对实验室信息、实验项目和实验预约情况的查询以及对实验室的预约…

LeetCode 69—— x 的平方根

阅读目录 1. 题目2. 解题思路一3. 代码实现一4. 解题思路二5. 代码实现二 1. 题目 2. 解题思路一 二分查找法&#xff0c;对于整数 i ∈ [ 0 , x ] i \in [0,x] i∈[0,x]&#xff0c;我们判断 i 2 i^2 i2 和 x x x 的关系&#xff0c;然后找到最后一个平方小于等于 x x x …

向量语义学

书籍&#xff1a;Vector Semantics 作者&#xff1a;Andrs Kornai 出版&#xff1a;Springer Singapore 书籍下载-《向量语义学》本书通过提出一个以线性多面体术语表达的形式理论来弥合这一差距&#xff0c;该理论将字向量和概念结构进行了概括&#xff0c;将每个词典定义视…

45. UE5 RPG 使用元属性(Meta Attributes)以及使用Set by Caller修改伤害

在RPG游戏中&#xff0c;我们是不会直接修改生命值的属性&#xff0c;是因为在修改角色属性时&#xff0c;需要获取角色的属性并进行复杂的计算&#xff0c;所以&#xff0c;我们正常情况下使用元属性&#xff08;Meta Attributes&#xff09;作为计算的中间的媒。在服务器上先…

前端-React项目初始化

大家好我是苏麟 , 今天聊聊前端依赖 Ant Desgin Pro 快速初始化项目 . Ant Desgin Pro 官网 : 开始使用 - Ant Design Pro 初始化项目 找到文档->快速上手 脚手架命令 : # 使用 npm npm i ant-design/pro-cli -g创建项目命令 : pro create 项目名称 选择简单还是全量 : …

Python | Leetcode Python题解之第64题最小路径和

题目&#xff1a; 题解&#xff1a; class Solution:def minPathSum(self, grid: List[List[int]]) -> int:if not grid or not grid[0]:return 0rows, columns len(grid), len(grid[0])dp [[0] * columns for _ in range(rows)]dp[0][0] grid[0][0]for i in range(1, r…

五大开放式耳机推荐,选对耳机让运动更带感!

看似精彩的户外运动经历背后&#xff0c;其实是枯燥的体能运动和训练&#xff0c;以及独自长途和长时间旅行伴随的孤独感&#xff0c;而排解这些不良情绪的最佳方式就是音乐。如果你希望在运动、舒适、安全和音质之间获得一个最佳平衡&#xff0c;那相比入耳式耳机&#xff0c;…

71.网络游戏逆向分析与漏洞攻防-角色与怪物信息的更新-分析并利用角色与怪物创建的数据包

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 现在的代码都是依据数据包来写的&#xff0c;如果看不懂代码&#xff0c;就说明没看懂数据包…

『项目整理』易CAR通项目说明文档-我的第一款APP

『项目整理』易CAR通项目说明文档-我的第一款APP 项目介绍功能介绍技术栈介绍实现效果如何运行备注 项目介绍 易CAR通项目是我的第一个Android项目。是一款结合了AR技术的模仿懂车帝的看车软件。因为是初学&#xff0c;所示实现的效果差强人意&#xff0c;很多的功能界面只实现…

文件批量高效管理,批量将PDF类型文件移动到指定文件夹里,实现文件高效管理

文件的管理与整理成为了我们生活中不可或缺的一部分。面对堆积如山的PDF文件&#xff0c;你是否也曾感到手足无措、焦头烂额&#xff1f;现在&#xff0c;有了我们的批量文件管理工具&#xff0c;PDF文件的管理将变得前所未有的高效与简单&#xff01; 首先&#xff0c;我们要…

对于Servlet项目无法显示css样式

有可能你在过滤器中拦截了.css文件&#xff0c;并且你设置了响应格式导致的&#xff0c;就比如我的项目如下图所示 删除servletResponse.setContentType("text/html; charsetUTF-8");即可

Android C++ 开发调试 LLDB 工具的使用

文章目录 调试环境准备基础命令Breakpoint CommandsWatchpoint CommandsExamining VariablesEvaluating ExpressionsExamining Thread StateExecutable and Shared Library Query Commands 参考&#xff1a; Android 中在进行 NDK 开发的时候&#xff0c;我们经常需要进行 C 代…

MySQL——88张表汇总——DDL+外键

外键er图 88张表 /* Navicat MySQL Data TransferSource Server : MyList Source Server Version : 50726 Source Host : localhost:3309 Source Database : schooldbTarget Server Type : MYSQL Target Server Version : 50726 File Encoding …

【C/C++基础实战】:用C++实现通讯录管理系统——含完整源码

文章目录 通讯录管理系统一、系统需求以及成品演示二、代码实现三、完整代码 通讯录管理系统 一、系统需求以及成品演示 1.1 系统需求 通讯录是一个可以记录亲人、好友信息的工具。这里利用C来实现一个通讯录管理系统 系统中需要实现的功能如下&#xff1a; 添加联系人&am…

《HCIP-openEuler实验指导手册》2.1安装和测试Nginx

知识点 Nginx (发音为 “engine x”) 是一个开源的高性能 HTTP 和反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。由 Igor Sysoev 创建并维护&#xff0c;其设计用于处理高并发连接&#xff0c;具有高度的可扩展性和灵活性。 安装步骤 yum方式安装 dn…