从开始到贪吃蛇

Day 2025.1.21

unity方法

关于transform组件的使用

直接使用transform组件

transform.Translate()

用于当前脚本挂载组件的移动

player = this.gameObject;

用于挂载当前实体对象

Day 2025.1.23

关于c#面向对象

Public ObjState{

Move,

Stop,

Lockmove

}

表示ObjState只有3个状态

If(ObjState == move){

Num++;

}

C# class类的学习

Day 2025.1.24

Static 一般的变量要在实例化类后才能访问,加了static后不实例化也可以访问

Day 2025/1/24

Materials  材质包

关于unity的生命周期

Void Start(){}  在游戏开始时运行

Void update(){} 每一帧重复执行的函数,一般来说一个游戏每秒60

 

关于unity的控制台

debug.log(“”)输出

 

Vector3

unity中的一种向量类,用于表示位置,方向和运动等概念

// 给rd施加一个向右的力,默认为1牛的力

        rd.AddForce(Vector3.right);

        // 施加2牛的力

        rd.AddForce(new Vector3(2,0,0));

 

Input.getAxis(Horizontal) 获取当前键盘键入字符,水平

Input.getAxis(Vertical) 获取当前键盘键入字符,垂直

 

碰撞发生的三个事件:碰撞、碰撞中、碰撞后

private void OnCollisionEnter(Collision collision) {

        Debug.Log("碰撞检测.");

    }

private void OnCollisionExit(Collision collision) {

        Debug.Log("碰撞检测.");

    }

    private void OnCollisionStay(Collision collision) {

        Debug.Log("碰撞检测.");

    }

Day 2025/1/28

今天学习游戏碰撞检测

当前模型碰撞检测:

Private void OnCollisionEnter(Collision collisio){

If(collisio.gameObject.tag == food){

 

}

}

Destory(collision.gameObject)  摧毁模块

 

触发检测:跟碰撞检测差不多,区别是触发检测没有实物碰撞

private void OnTriggerEnter(Collider other) {

        if (other.gameObject.tag == "food")

        {

            Destroy(other.gameObject);

        }

    }

触发检测

碰到物体后销毁及增加分数

引入命名空间

using UnityEngine.UI;

获取分数组件

定义分数变量

Int score = 0

Public Text scoreText;

更新分数

Score++

scoreText.text = XXXXX

 

游戏胜利后显示胜利文本

winText.SetActive(true);

 

 

完成

Day 2025/1/29

Time.deltatime 每一帧之间执行的时间

1/deltatime 则是每秒执行的帧数

void Update()

    {

        float h = Input.GetAxis("Horizontal");

        float v = Input.GetAxis("Vertical");

        transform.Translate(new Vector3(h,v,0) * speed * Time.deltaTime);

    }

使用算法实现模块每秒执行速度

也可以用生命周期FixedUpdate

private void FixedUpdate() {

       

    }

修正执行速度,大多数用算法

 

通过预制体+脚本新建组件(子弹)

public GameObject bulletPrefab;

    void Start()

    {

        GameObject.Instantiate(bulletPrefab,transform.position,transform.rotation);

    }

void Update()

    {

        if (Input.GetMouseButtonDown(0))

        {

            GameObject.Instantiate(bulletPrefab,transform.position,transform.rotation);

        }

    }

检测鼠标是否按下后创建子弹

    void Update()

    {

        if (Input.GetMouseButtonDown(0))

        {

            GameObject bullet = GameObject.Instantiate(bulletPrefab,transform.position,transform.rotation);

            Rigidbody rd = bullet.GetComponent<Rigidbody>();

            // rd.AddForce(Vector3.forward*100);

            rd.velocity = Vector3.forward*30;

        }

    }

获取刚体组件并且施加前进的力

Rd.velocity = vector3.forward

Day 2025/1/30

学习GUI

今日主要重点:查找组件find方法

Day 2025/2/1

关卡选择的制作:

设置背包面板为预制体,实例化预制体,然后与预制体断开链接,删除背包内元素

设置超宽的关卡选择,然后使用scroll组件再使用mask组件进行滑动管理

实现关卡选择页功能

  1. 使用scroll rect自带的value chanhed事件
  2. 使用拖拽接口

 

 

 

 

public class levelScroll : MonoBehaviour,IBeginDragHandler,IEndDragHandler

{

   

    public void OnBeginDrag(PointerEventData eventData)

    {

        throw new System.NotImplementedException();

    }

 

    public void OnEndDrag(PointerEventData eventData)

    {

        throw new System.NotImplementedException();

    }

}

 

  1. 获取scroll组件scroll = getcomponet
  2. 定义一个float数组,当scroll.horizontalnormalizedposition == 14分之一的相近值则跳到四分之一
  3. 使用线性函数math.lerp实现页面切换功能

public void OnEndDrag(PointerEventData eventData)

    {

        float currentPosition = scroll.horizontalNormalizedPosition;

        if (currentPosition<0.25)

        {

            index= 0;

        }

        if (currentPosition>0.25 && currentPosition<0.75)

        {

            index = 1;

        }

        if (currentPosition>0.75)

        {

            index = 2;

        }

        // 指定页

       

        // scroll.horizontalNormalizedPosition = pagePosition[index];

        isMoving = true;

    }

  1. 原视频用了比较复杂的算法,这边用的是自己觉得比较简单的方法

void Update()

    {

        // 如果正在移动,则使用线性插值函数移动

        // 线性函数用法:Mathf.Lerp(起始值,目标值,速度)

 

        if (isMoving)

        {

            scroll.horizontalNormalizedPosition =    Mathf.Lerp(scroll.horizontalNormalizedPosition,pagePosition[index],Time.deltaTime*speed);

            if (Math.Abs(scroll.horizontalNormalizedPosition-pagePosition[index])<0.001f)

            {

                isMoving = false;

                scroll.horizontalNormalizedPosition = pagePosition[index];

            }

        }

    }

  1. 使用了线性函数切换页

Day 2025/2/2

切换按钮

使用togle group的组件实现

Togle按钮下有onvaluechanged方法。分别给三个按钮绑定方法

public void moveToPage1(bool isOn){

if (isOn)

        {

            isMoving =true;

            index = 0;

        }

 

    }

    public void moveToPage2(bool isOn){

       

    }

    public void moveToPage3(bool isOn){

       

    }

相应的,srcoll拉到相应位置选中的按钮也随之变化

public Toggle[] toggleArray;

toggleArray[index].isOn = true;

 

 

关于任务列表的制作:使用vertical-layyout-group组件

滚动scroll+mask的制作:新建一个image添加scrollrectmask组件然后showmaskgraphic取消选择然后将grid或者layoutgroup拖到content

Setting界面的制作

  1. 声音使用slider组件
  2. 难度使用toggletoggle group组件

声音开关控制

使用toggle控件onvaluechanged方法控制,开和关两个选项显示

登录框

使用inputfield组件

 

---完成

Day 2025/2/3

坦克大战

文件夹分类

Animation动画

AnimationController动画控制器

Prefab预制体下

Effect特效

Map地图

玩家移动

依旧用wasd水平垂直轴移动

    void Update()

    {

        float h = Input.GetAxisRaw("Horizontal");

        float v = Input.GetAxisRaw("Vertical");

        transform.Translate(Vector3.right*h*Time.deltaTime*speed);

        transform.Translate(Vector3.up*v*Time.deltaTime*speed);

    }

还要控制玩家贴图的旋转

引入sprite render组件和新建一个sprite数组,放入上下左右四张贴图

H<0则为向左

H>0为向右

V<0==down

V>0==up

 

使用awake生命函数初始化时获取渲染组件sprite render

添加碰撞器和刚体

Boxcliderrigbody

  1. 解决碰撞物体后玩家会旋转:取消碰撞器中的z
  2. 解决物体碰撞抖动:由于update根据电脑性能不同每一帧执行的间隔不一样,使用fixedupdate生命周期
  3. 解决物体下坠:取消重力
  4. 禁止玩家斜向移动:当横向移动,也就是h!=0时,直接返回,不执行纵向移动

if (h!=0)

        {

            return;

        }

 

代码优化:将玩家移动添加到playerMove方法中

private void Move(){}

渲染层级:将重生点的层级设置到玩家之上

Order in layer = 1

子弹的层级也是1

子弹生成

instantiate方法:

Instantiate(预制体,位置,自旋转)

  1. 解决子弹不动的问题
  2. 解决子弹朝向问题

Day2025/2/4

给子弹组件施加一个向前的力,往朝向方向移动

transform.Translate(transform.up*bulletSpeed*Time.deltaTime,Space.World);

 

设置子弹速度和射击间隔

void Update()

    {

        // 设置子弹射击间隔0.4s

        if (timeVal>=0.4f)

        {

            Shoot();

            timeVal = 0;

        }else{

            timeVal+=Time.deltaTime;

        }

    }

 

void Update()

    {

        transform.Translate(transform.up*bulletSpeed*Time.deltaTime,Space.World);

    }

给子弹添加触发器boxcolider和刚体rigidbody -完成

物体添加tag --完成

射击检测和空气墙 --完成

编写玩家被击中-销毁状态

    private void Die(){

        if (isDefend)

        {

            return;

        }

        // 生成爆炸特效

        Instantiate(deathAnimation,transform.position,transform.rotation);

        // 摧毁组件

        Destroy(gameObject);

    }

在子弹脚本中调用死亡函数

    private void OnTriggerEnter2D(Collider2D collision) {

        switch (collision.tag)

        {

            case "wall":

            break;

            case "enemy":

            break;

            case "obst":

            break;

            case "airObst":

            break;

            case "tank":

            // 使用玩家组件下的die方法

            collision.SendMessage("Die");

            break;

        }

        Destroy(gameObject);

    }

在子弹脚本中定义是否为玩家子弹的变量

 

If(!isTankBullet)collision.SendMessage("Die") 

编写heart被摧毁脚本

Destory(collision.gameobject)

Destory(gameobject)

除了子弹,其他组件不需要勾选is trigger

今天就纠结这个istrigger了,学了个锤子

Day 2025/2/6

喝了酒,犯困,什么都没学

Day 2025/2/6

敌人的制作,家爆炸特效以及出生特效

前两个没什么好说的,敌人的脚本跟玩家的差不多

家爆炸特效是在heart变更特效图后生成爆炸特效

延时出生和延时销毁出生特效

    void Start()

    {

        // 延时生成和延时销毁

        Invoke("Generate",0.7f);

        Destroy(gameObject,0.7f);

    }

    private void Generate(){

        Instantiate(tankPrefab,transform.position,Quaternion.identity);

    }

1. 新建一个玩家子弹模板,敌人子弹模板,玩家模板,敌人1模板,敌人2模板

2. 在出生特效模板中定义一个玩家模板,一个敌人数组,

if createPlayer则生成玩家,else生成随机类型敌人

3. 编写敌人AI,每隔3秒射击子弹,每4秒移动一次需要用到随机函数

int num = Random.Range0,8)  //不包括8

bug:包括敌人tag,子弹tag,子弹脚本-击中敌人

 

编写地图生成脚本

新建一个空组件createMap

先生成家

注意:awake函数执行在start函数之前,所以生成地图使用awake函数

组件生成后设置父组件

GameObject itemGo =  Instantiate(obj,position,rotation);

        itemGo.transform.SetParent(gameObject.transform);

编写地图产生随机障碍物和空气墙

定义一个已有位置数组

一般来说,墙生成60个其他物体生成20

生成玩家和敌人

生成born特效,设isPlayertrue

最开始顶上3个位置生成3个敌人

然后每隔5秒生成1个敌人

InvokeRepeating("createEnemy",4,5);

修复bug:敌人碰到会推着走

设置敌人碰撞检测:让敌人碰到后跳过移动间隔,设置一开始敌人向下走,减少碰撞

玩家状态管理

敌人脚本-死亡得分

玩家脚本-死亡就调用 状态管理death = true

 

把玩家状态创建为一个全局单例--即不用引入到其他脚本中就可以使用

    private static status instance;

    public static status Instance

    {

        get

        {

            return instance;

        }

        set

        {

            instance = value;

        }

    }

    private void Awake() {

        instance = this;

}

//固定写法

 

状态管理中玩家有3条血,死亡一次--

Day2025/2/7

修复:家被击毁,禁止玩家发射和移动行为

Heart--die--status--isDefeat = true

Player--fixedupdate--status.isdefeat

游戏背景的制作,玩家得分和生命显示,游戏失败显示

要在UI里面新建组件

首页制作和音效

有首页的光标选择

w切换光标位置1s切换光标位置2

Flag1时切换scene1

然后就是切换场景sceneManager

返回主页

视频里用的是3秒后自动返回主界面,我们可以设置空格键返回

 

Heart引入音效组件

public AudioClip dieAudio;

直接播放音效

AudioSource.PlayClipAtPoint(dieAudio,transform.position);

坦克死亡:直接在爆炸特效上添加一个音频组件,生命周期开始时只播放一次

打到障碍物:在子弹脚本上调用障碍物的播放音效方法

Day 2025/2/8

制作子弹音效

直接在子弹上添加音效组件

玩家移动音效

当一个组件上有多个音效:先在脚本内定义音效数组,然后添加音效组件,控制组件的播放切换

        if (Math.Abs(h)<0.05f)

        {

            currAudio.clip = playerAudio[0];

            if (!currAudio.isPlaying)

            {

                currAudio.Play();

            }

        }

扩展功能

1.主页面渐入效果

可以用animation组件也可以用脚本做

2.玩家2功能-edit-projectsetting-input中新增玩家2按键

3.其他坦克的扩展,奖励品功能

画布自适应

Scenes-canvasScaler-scalermode-scaleWithScreenSize

完成

Day 2025/2/9

学习贪吃蛇

2d游戏cameraMode一般为camera,然后把camera拖到canvas

3d游戏一般为overlay

Day 2025/2/10

贪吃蛇

Day 2025/2/12

继续贪吃蛇

在四周做一个隐藏的边界,创建一个空组件然后设置10高度满长度添加碰撞检测

Boxcolider设置size XY

 

蛇头的移动,这里视频把蛇的移动间隔用循环定时器,监听键盘用update

每隔(velTime)秒移动x step y step

上为x = 0y+step,下为x=0y-step类推

lastPos = gameObject.transform.localPosition;

gameObject.transform.localPosition = new Vector3(lastPos.x+x,lastPos.y+y,lastPos.z);

Day 2025/2/13

2d组件的旋转必须是四元数:

Quaternion.euler(0,0,0)

头贴图旋转并且不能直接向后转

Transform.localrotation = Quaternion.Euler(0,0,0)

If(keycode.w && x!=-step)

蛇加速

停止循环定时器,并修改移动延时,然后重启定时器

第一个食物的生成

食物生成器food

  1. 食物种类随机数
  2. 随机位置
  3. 设置父组件及取消自动缩放

后面的生成视频中采用的是食物销毁后生成食物,但是也可以用食物上限检测的方法来做

单例化createFood脚本

注意:要setparent,不然生成的食物没有位置锚点,生成不知道哪里去

蛇身的生成

确定当前要增加的是奇数身段还是偶数,设置父组件

生成的身段添加到bodyList数组中

Day 2025/2/14

蛇身的移动逻辑

吃食物-蛇身数组+1-蛇头每帧移动时将蛇身最后一节放到蛇头上一个位置,就有移动的视觉错句,弊端是蛇身颜色每一帧是变化的

Bug

  1. 蛇身颜色会不停变换
  2. 吃完食物蛇身会先生成在中央位置--生成的蛇身先放在屏幕外

Day 2025/2/15

方法二:

将尾部移动到头部只能适用于单色的,蛇身是两个颜色就会造成颜色不停变换

要将蛇身每个部位向前移动

            for (int i = body.Count-1; i < 0; i--)

            {

                body[i].localPosition = body[i-1].localPosition;

            }

            body[0].localPosition = headPos;

 

边界死亡和自由模式

死亡

Tag-body

Ontrigger if (collision.tag == tag)

注意修改边界尺寸也要把碰撞尺寸一起修改!不然没办法触发碰撞器!!!

 

自由模式

主要是根据蛇头碰到gameobject.name为上边界时则将y轴转换正负数

然后左右边界有偏移量所以转换的时候要加上偏移量

直接转没有效果是因为直接转会反复触发上下边界的判定,所以由上边界转换到下边界时要步数+1

奖励目标的生成和获取

就是生成食物有一个传参,是否为奖励品

吃掉食物时有20%几率为真

Day 2025/2/16

分数和长度记录和背景切换

UIcontroller依旧实例化

三个文本:长度文本,分数文本,阶段文本

 

 

切换背景颜色并设置透明度的方法,但是我并没有用,只是用普通的color.red

Image image = GetComponent<Image>();

// 设置颜色为红色,透明度为55%

image.color = new Color(1, 0, 0, 0.55f); // R, G, B, A (红, 绿, 蓝, 透明度)

暂停游戏与返回菜单

引入菜单按钮和暂停按钮

使用Time.scale(0)控制时间暂停

同时暂停的图片也要切换,用sprite数组引入图片

 

暂停按钮冲突BUG

将快捷键中的提交submit空格键取消

Edit-projectsetting-input

并且加速的时候还要有一个当前是否暂停游戏的判断

主菜单:将两个场景在scene setting中设置

Day 2025/2/17

蛇死亡和游戏得分的记录

结束循环定时器,并且用系统自带的得分记录功能储存得分

死亡调用

这个是类似html的本地储存,用法playerprefabs.setInt(键,值)

        PlayerPrefs.SetInt("lastl",mainUI.Instance.length);

        PlayerPrefs.SetInt("lasts",mainUI.Instance.score);

//判断是否是最高得分

        if (PlayerPrefs.GetInt("bests",0)<mainUI.Instance.length)

        {

            PlayerPrefs.SetInt("bestl",mainUI.Instance.length);

            PlayerPrefs.SetInt("bests",mainUI.Instance.score);

        }

    }

然后用一个协成做游戏等待后重开

        StartCoroutine(gameOver(1.5f));

    }

    IEnumerator gameOver(float t){

        yield return new WaitForSeconds(t);

        SceneManager.LoadScene(1);

}

//等待1.5秒后重载游戏场景

用户设置的储存

上次得分和最后得分在组件加载时就调用脚本

用户选择皮肤和模式

在按钮组件中绑定方法,并传入isOn参数

 

并且游戏进入时读取上一次的用户选择

进入游戏后加载选中的皮肤

新建Resoures文件夹并把所有皮肤放进去

游戏开始时蛇头awake函数直接替换资源

if (PlayerPrefs.GetString("sb") == "sb01")

        {

            gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>("sh01");

            bodySprite[0] = Resources.Load<Sprite>("sb0101");

            bodySprite[1] = Resources.Load<Sprite>("sb0102");

        }

        else

        {

            gameObject.GetComponent<Image>().sprite = Resources.Load<Sprite>("sh02");

            bodySprite[0] = Resources.Load<Sprite>("sb0201");

            bodySprite[1] = Resources.Load<Sprite>("sb0202");

        }

 

 

边界的话就是mainUI脚本中判断是否为自由模式

是的话直接遍历bg下的边界组件enablefalse

 

在蛇头中也要判断后撞到边界直接死亡

    void Start()

    {

        if (PlayerPrefs.GetString("mode") == "free")

        {

            isFree = true;

            foreach (Transform t in bg.gameObject.transform)

            {

                t.gameObject.SetActive(true);

            }

        }else{

            isFree = false;

            foreach (Transform t in bg.gameObject.transform)

            {

                t.gameObject.SetActive(false);

            }

        }

}

//遍历组件

 

 

播放音效

public AudioClip eataudio;

AudioSource.PlayClipAtPoint(eataudio,Vector3.zero);

 

Day 2025/2/18

在做贪吃蛇的复盘,计划明天学愤怒小鸟

PVZ也可以,whatever

RPG游戏也可以

https://www.bilibili.com/video/BV1YG411a7gV?spm_id_from=333.788.videopod.episodes&vd_source=e77595c440717d27f09cf27566810c55&p=65

Day 2025/2/19

出去玩了

Day 2025/2/20

完成贪吃蛇复盘,进度太慢了,要加快了

 

posted @ 2025-03-15 21:34  无法言语的寂灭之形  阅读(90)  评论(0)    收藏  举报