从开始到贪吃蛇
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组件进行滑动管理
实现关卡选择页功能
- 使用scroll rect自带的value chanhed事件
- 使用拖拽接口
public class levelScroll : MonoBehaviour,IBeginDragHandler,IEndDragHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
throw new System.NotImplementedException();
}
public void OnEndDrag(PointerEventData eventData)
{
throw new System.NotImplementedException();
}
}
- 获取scroll组件scroll = getcomponet
- 定义一个float数组,当scroll.horizontalnormalizedposition == 1的4分之一的相近值则跳到四分之一
- 使用线性函数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;
}
- 原视频用了比较复杂的算法,这边用的是自己觉得比较简单的方法
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];
}
}
}
- 使用了线性函数切换页
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添加scrollrect和mask组件然后showmaskgraphic取消选择然后将grid或者layoutgroup拖到content内
Setting界面的制作
- 声音使用slider组件
- 难度使用toggle和toggle 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
添加碰撞器和刚体
Boxclider和rigbody
- 解决碰撞物体后玩家会旋转:取消碰撞器中的z轴
- 解决物体碰撞抖动:由于update根据电脑性能不同每一帧执行的间隔不一样,使用fixedupdate生命周期
- 解决物体下坠:取消重力
- 禁止玩家斜向移动:当横向移动,也就是h!=0时,直接返回,不执行纵向移动
if (h!=0)
{
return;
}
代码优化:将玩家移动添加到playerMove方法中
private void Move(){}
渲染层级:将重生点的层级设置到玩家之上
Order in layer = 1
子弹的层级也是1
子弹生成
instantiate方法:
Instantiate(预制体,位置,自旋转)
- 解决子弹不动的问题
- 解决子弹朝向问题
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.Range(0,8) //不包括8
修bug:包括敌人tag,子弹tag,子弹脚本-击中敌人
编写地图生成脚本
新建一个空组件createMap
先生成家
注意:awake函数执行在start函数之前,所以生成地图使用awake函数
组件生成后设置父组件
GameObject itemGo = Instantiate(obj,position,rotation);
itemGo.transform.SetParent(gameObject.transform);
编写地图产生随机障碍物和空气墙
定义一个已有位置数组
一般来说,墙生成60个其他物体生成20个
生成玩家和敌人
生成born特效,设isPlayer为true
最开始顶上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切换光标位置1,s切换光标位置2
Flag为1时切换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 = 0,y为+step,下为x=0,y为-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
- 食物种类随机数
- 随机位置
- 设置父组件及取消自动缩放
后面的生成视频中采用的是食物销毁后生成食物,但是也可以用食物上限检测的方法来做
单例化createFood脚本
注意:要setparent,不然生成的食物没有位置锚点,生成不知道哪里去
蛇身的生成
确定当前要增加的是奇数身段还是偶数,设置父组件
生成的身段添加到bodyList数组中
Day 2025/2/14
蛇身的移动逻辑
吃食物-蛇身数组+1-蛇头每帧移动时将蛇身最后一节放到蛇头上一个位置,就有移动的视觉错句,弊端是蛇身颜色每一帧是变化的
Bug:
- 蛇身颜色会不停变换
- 吃完食物蛇身会先生成在中央位置--生成的蛇身先放在屏幕外
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下的边界组件enable为false
在蛇头中也要判断后撞到边界直接死亡
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游戏也可以
Day 2025/2/19
出去玩了
Day 2025/2/20
完成贪吃蛇复盘,进度太慢了,要加快了