Little Vampire
Unity制作2D小游戏
Day1
尝试一天速通C#
前面的都好办,和C++啥的都大差不差,后面的给我看的云里雾里,要用的话再看吧
然后为了练练手去cf上找了俩简单题写
1594A,下面是用三种语言写的差别:
乐
然后还有一道1709B:
只能说差距显著
C#读入数据真的很不方便,对于一次输入很多数据,中间用空格间隔的那种,C#基本就废了
最后我是Console.ReadLine()一整行,然后另定义了一个string[],把读入的一整行Split了,然后遍历一遍Convert到数组里
就离谱啊🙃
下面是1709B用C#写的屎山
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cftime
{
class Helloworld
{
static void Main(string[] args)
{
int t;
t = 1;
long[] a = new long[200005];
long[] dam = new long[200005];
long[] dam2 = new long[200005];
//t = Convert.ToInt32(Console.ReadLine());
while (t != 0)
{
t--;
int n, m;
string wtf = Console.ReadLine();
string[] wocao = wtf.Split();
n = Convert.ToInt32(wocao[0]);
m = Convert.ToInt32(wocao[1]);
wtf = Console.ReadLine();
wocao = wtf.Split();
for (int i = 1; i <= n; i++)
a[i] = Convert.ToInt64(wocao[i - 1]);
for(int i = 2; i <= n; i++)
{
if (a[i] <= a[i - 1])
dam[i] += a[i - 1] - a[i];
dam[i] += dam[i - 1];
}
for(int i = n - 1; i >= 1; i--)
{
if (a[i] <= a[i + 1])
dam2[i] += a[i + 1] - a[i];
dam2[i] += dam2[i + 1];
}
while (m-- != 0)
{
int l, r;
wtf = Console.ReadLine();
wocao = wtf.Split();
l = Convert.ToInt32(wocao[0]);
r = Convert.ToInt32(wocao[1]);
if (l <= r)
Console.WriteLine("{0}", dam[r] - dam[l]);
else
Console.WriteLine("{0}", dam2[r] - dam2[l]);
}
}
}
}
}
Day2
找了很多Unity教程,我最开始的想法是直接找一个像素rpg的教程,方便我自己写抄。在b站翻了半天也没有称心的. 最后还是选了一个普通的2d横板游戏的教程(BV1W4411Z7UC),希望我看完后能写小rpg.
讲的很细,跟着做的时候基本没有出现问题. 今天跟着视频做了素材导入/画地图/图层/角色的左右朝向and水平移动and跳跃下落.
素材导入:
去Assets store下载,导就完事了.
画地图:
左侧SampleScene创建2D Object→Tilemap;顶部栏的Window→2D→Tile Palette打开一个窗口,之后要把背景素材导入进去(记得新建一个map文件夹).
导入素材前,需要把素材分割成单元,方便更好的绘图:点击素材的tileset→右侧inspector里的Sprite Mode调成Multiple→点击Sprite Editor进入编辑界面→顶部栏Slice的Type换成Grid By Cell Size,Pixel Size取俩16(因为我们的一个Unit取的是16个像素).
分割好后把素材拖到Tile Palette里,就可以在Scene里画图了.
图层:
地图和背景需要分图层。
Sorting Layer的下拉菜单里点Adding Sorting Layer,在里面建俩Sorting Layer:Background和Frontground。回到原菜单,给地图和背景选择Sorting Layer,选项里越靠下的越在前面.
也可以调Order in Layer.
Player:
导进来,Inspector的Rigibody 2D里把刚体的Freeze Rotation给选上,否则后面移动的时候会鬼畜.
Inspector下Add Component添加Capsule Collider 2D,这里教程用的是Box Collider 2D,但是好像有点问题. 点击一下Edit Collider调整碰撞体
同时还要给地图加碰撞体. 在Tilemap里Add Compoment 2D添加Tilemap Collider 2D
接下来要写移动,有关移动的名称/按键在顶部栏Edit→Project Setting→Input Manager里的Axes里查看. Player里Add组件Script,建议创一个Script文件夹把所有Script都放进去. 下面开始写码
移动:
首先要声明很多
1 2 3 4 5 6
public Rigidbody2D rb;//Player public Animator anim;//动画 public Collider2D coll;//Player 碰撞体 public float speed;//移速 public float jumpforce;//跳跃 public LayerMask ground;//地图的碰撞体
这里要注意在Inspector里给设置一下,拖拽就行
下面是函数部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
void Movement() { float horizontalmove = Input.GetAxis("Horizontal");//会获得-1~1的值 float facedircetion = Input.GetAxisRaw("Horizontal");//会获得-1、0、1中的一个值 if (horizontalmove != 0)//水平移动 { rb.velocity = new Vector2(horizontalmove * speed, rb.velocity.y); anim.SetFloat("running", Mathf.Abs(facedircetion)); } //player朝向 if (facedircetion != 0) transform.localScale = new Vector3(facedircetion, 1, 1); // 跳跃 if (Input.GetButtonDown("Jump")) { rb.velocity = new Vector2(rb.velocity.x, jumpforce); anim.SetBool("jumping", true); } }
动画:
Player里Add组件Animator,创建文件夹Assets→Animation→Player→Player(前面的都是文件夹,这个是Animator Controller),拖拽这个Controller到Player的Script里(上面有图显示了)
Window→Animation→Animation打开时间轴窗口,创建动画idle,把idle每一帧都导进去,调整时间。再开一个窗口:Window→Animation→Animator,这里能连线,做细节. 添加新动画,先在SampleScence里选中Player,然后就在时间轴那个窗口里添加就行.
动画的转换,可见下图
这里要设置连线的Conditions设置,如jump到fall的线是falling is true && jumping is false,falling和jumping是在左侧Parameters里设置的bool量。
对于跳跃动画,fall转idle需要判断是否落地,也就是判断Player的碰撞体下端碰到地图的碰撞体了,得先设置一下地图的碰撞体.
Player的Script也要设置一下(上面的一个图里有),其余就是看代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
void SwitchAnim()//切换动画这里结合Animator看 { anim.SetBool("idle", false); if (anim.GetBool("jumping")) { if (rb.velocity.y < 0) { anim.SetBool("jumping", false); anim.SetBool("falling", true); } } else if (coll.IsTouchingLayers(ground))//判断是否触地 { anim.SetBool("falling", false); anim.SetBool("idle", true); } }
全图:
Day3
镜头跟随:
普通的办法是:给Main Camera新建一个Script,在里面获取Plaer的transform并Update到Main Camera.
1 2 3 4 5
public Transform player; void Update() { transform.position = new Vector3(player.position.x, player.position.y, -10f); }
更方便,效果更好的方法是利用Cinemachine的2D Camera. 在右侧SampleScene新建Cinemachine→2D Camera,将其Follow设置为Player即可. 在Body里有不少东西可以调整.
背景图ctrl+d复制几个拉长,新建Create Empty把所有的背景图都拉进去,给BackGround添加Polygon Collider 2D组件,调整边框到背景图的边界,勾上”Is Trigger”,拖到摄像头的Bounding Shape 2D里,这样做可以使摄像头不会超出背景图,即会被背景的碰撞体约束.
镜头会晃动,有解法是把 CinemachineVirtualCamera 里三个轴的Damping都设置成0,我试了确实可以,但是镜头移动没有平滑感了,不大行. 最后改了CinemachineBrain里的Update Method为Fixed Update,效果很好.
物品收集:
先是添加物品及加动画:新建Sprite,添加Animator组件,在文件夹Animation里新建Collections文件夹,里面新建Animator Controller,拖拽到Animator里. 新建Animation并加上动画.
给物件添加Box Collider 2D组件,勾选”Is Trigger”.
给物件添加Tag:”Collection”,在Player Controller的脚本里添加函数.
1 2 3 4 5 6 7 8
private void OnTriggerEnter2D(Collider2D collision) { if(collision.tag == "Collection") { Destroy(collision.gameObject); Cherry++; } }
即Player与物件碰撞后销毁物件并计数.
Assets里新建文件夹Perfabs(预制品),把SampleScene里的物件拖进去,这样方便使用.
跳跃部分的修改:
解决撞墙不掉落的问题:在Assets里新建物理材质Physics Material 2D,Friction调成0,拖到Player的碰撞体里. Player在斜坡上会滑,可以用俩碰撞体来解决.
我稍微改了一点教程里的,添加了从idle到fall的箭头,毕竟这是可能出现的情况.
解决无限跳的问题:判定触地了才能跳.
1
if (Input.GetButtonDown("Jump") && coll.IsTouchingLayers(ground))
动画延迟,在线那里的has exit time取消掉.
下蹲:
导动画素材.
按下S时把Player头部的碰撞体给取消掉,这样Player只有下面的碰撞体,可以穿过一格小的空间.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
... public Collider2D Crouchcoll; ... if (Input.GetKey(KeyCode.S)) { speed = 6f;//蹲下的时候速度当然慢啦 Crouchcoll.enabled = false;//取消头部碰撞体 anim.SetBool("crouch", true); } else { speed = 12f; anim.SetBool("crouch", false); Crouchcoll.enabled = true; }
物品收集数量显示:
SampleScene里新建UI→Canvas,里面新建UI→Legacy→Text. 分为不变的和变化的文本.
改一下脚本.
1 2 3 4 5 6 7 8 9 10 11 12 13
... public Text CherryNum; ... private void OnTriggerEnter2D(Collider2D collision)//收集物品 { if(collision.tag == "Cherry") { Destroy(collision.gameObject); Cherry++; CherryNum.text = Cherry.ToString(); } }
在Player的脚本里把Text拖过去.
留个全图~
Day4
敌人(青蛙):
导模型、加动画.
设置为刚体,添加碰撞体.
简易AI:
■ 教程里的普通青蛙:
给青蛙建个脚本.
这两个标签的坐标会跟着Frog(其父亲)移动,所以在Start()里先获取标签的下标后保存起来.
判断青蛙与标签的坐标关系,更改图像和速度的方向.
■ 为啥说这是普通青蛙呢,这样的青蛙除了有idle的鼓嘴动作之外,就是很无聊的左右平移啊,素材里有jump的素材,可以利用一下.
各种游戏里青蛙移动方式似乎都是站着不动一会,然后跳一下,我们要实现的也是这样的.
首先得给青蛙弄个计时器.
1 2
public float times; public float clock = 3f;
先给times赋初值clock,在Update()里让times每次更新都-Time.deltaTime,也就是系统一个时间,当times<0时在给times赋值clock,这样就做出循环了.
times<0时,把anim的jump给开了,然后给青蛙速度赋个值、水平和竖直方向都要有. 竖直方向速度为负数时把anim的fall打开,触底时idle打开,关其他俩,同时速度归零.
图像转向的部分,要判断是触地了才能转向,否则在空中图像变了速度方向没变就很鬼畜…
效果图:
■ 后来我发现我做的这些都在后面的教程里有?好离谱…包括我做的那个从idle到fall的转化. 这青蛙的码塞到最后吧,好歹花了我不少时间.
■ 后面教程做的青蛙跳跃是用了event来延长动画,就是加了一个空的动画节点,在这个节点可以调用青蛙脚本里的Movement(),然后把Update()里的Movement()给删了,这样Movement()函数就不是反复执行,而是在特定时间节点才执行了. 和我那种方法感觉是各有优缺吧.
Hurt:
添加bool值hurt,true时播放hurt动画.
Hurt要反弹一下,具体代码见下(就硬写三目运算🤣).
1 2 3 4 5
if (!isHurt) { rb.velocity = new Vector2(5 * (transform.position.x < collision.gameObject.transform.position.x ? -1 : 1), rb.velocity.y); isHurt = true; Jump_();//反弹当然要跳一下~ }
因为Update()的问题,Hurt的动画可能会播不出来,在Update()里加个判断条件,不受伤的情况下才执行Movement().
1
if(!isHurt) Movement();
Hurt的刷新在SwitchAnim()里写.
1 2 3 4 5 6 7 8
if (isHurt) { anim.SetBool("hurt", true); if(Mathf.Abs(rb.velocity.x) < 0.1f) { anim.SetBool("idle", true); anim.SetBool("hurt", false); isHurt = false; } }
效果图:
父类继承and死亡动画:
敌人很多,但都有死亡动画. 且我们希望在Player的碰撞敌人函数里来执行敌人的动画.
新建脚本Enemy,这个Enemy要给其所有子类用,其Start()为protected virtual void Start(),virtual是为了子类也可以更改.
1
protected virtual void Start() {anim = GetComponent<Animator>();}
在Enemy里编写死亡动画部分,分两个
1
void Death() { Destroy(gameObject);}//销毁物件
1
public void JumpOn() { anim.SetTrigger("death");}//打开death的trigger
以青蛙为例,
其class改为:
1
public class Enemy_Frog : Enemy{...}// :是继承
其Start()改为:
1 2 3 4
protected override void Start(){ base.Start();//启动其父类的Start() ... }
因为用了其父类的Star(),anim被创建了,frog自己就无需提前创建anim.
然后在Player的脚本里,敌人交互部分,获取碰撞的敌人:
1
Enemy enemy = collision.gameObject.GetComponent<Enemy>();
判断是踩上去的后,直接就能用enemy的函数JumpOn()来启动敌人死亡.
再提一下这个东西是Trigger
死亡销毁物件:
给死亡动画的Animation加一个event,放在动画结束的地方,执行Death()函数.
音效:
BGM:给Player加Audio source,将音源拖进去,勾选Play On Awake(游戏开始时就播放),Loop(循环)
敌人爆炸:不要勾Play On Awake,打开Enemy脚本. 添加:
1 2 3 4 5 6 7
... protected AudioSource deathAudio; ... public void JumpOn() { anim.SetTrigger("death"); deathAudio.Play();//这里 }
跳跃音效:新建Audio Source,把音效拖进去,在Player脚本的跳跃部分播放音效,注意要把音效拖到Scprit里
收集物品的音效,我开始是给物品加了脚本,然后弄了好久也没成功,最后干脆直接给Player又加了一个音效(
其他:
- 改了踩敌人的判定,之前是当前动画为fall是可以踩,但由于我设置可以在空中蹲(高难动作钻缝),改成了判定当前速度为向下.
青蛙自动左右跳跃移动的码(没有继承Enemy的):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy_Frog : MonoBehaviour {
private Rigidbody2D rb;
public Transform leftpoint, rightpoint;
public float speed, jumpforce;
public int Face = 1;//1朝左,-1朝右
private float Leftx, Rightx;//左右边界的位置
private Animator anim;//动画
public float times;
public float clock = 3f;
public Collider2D coll;//Frog 碰撞体
public LayerMask ground;//地图的碰撞体
void Start() {
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
Leftx = leftpoint.position.x;
Rightx = rightpoint.position.x;
Destroy(leftpoint.gameObject);
Destroy(rightpoint.gameObject);
times = clock;
}
void Update() {
times -= Time.deltaTime;
Movement();
}
void Movement() {
//起跳
if (times <= 0) {
times = clock;
rb.velocity = new Vector2(speed * Face, jumpforce);
anim.SetBool("jump", true);
anim.SetBool("idle", false);
}
if (anim.GetBool("jump")) {
if (rb.velocity.y > 0) return;
//下落
if (rb.velocity.y < 0) {
anim.SetBool("jump", false);
anim.SetBool("fall", true);
}
}
//触地
if (coll.IsTouchingLayers(ground)) {
rb.velocity = new Vector2(0, 0);
anim.SetBool("fall", false);
anim.SetBool("idle", true);
}
//rb.velocity = new Vector2(speed*Face, rb.velocity.y);
if (!anim.GetBool("idle")) return;
if (Face==1) {
if (transform.position.x <= Leftx) {
Face *= -1;
transform.localScale = new Vector3(Face,1,1);
}
}
else {
if (transform.position.x >= Rightx) {
Face *= -1;
transform.localScale = new Vector3(Face, 1, 1);
}
}
}
}
Day5
Dialog:
Canvas里新建UI→Panel,命名EnterDialog,这个是对话框. 下面新建Text写内容.
给门(hourse)新建碰撞体和脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13
... public GameObject enterDialog; ... private void OnTriggerEnter2D(Collider2D collision) { if(collision.tag == "Player") { enterDialog.SetActive(true); } } private void OnTriggerExit2D(Collider2D collision) {//离开时 if (collision.tag == "Player") { enterDialog.SetActive(false); } }
要把那个EnterDialog拖到hourse的脚本里,初始把Active给关了.
设置对话框的出现对话:
蹲下的修改:
在障碍物下面蹲,起来会卡模型,不能让Player起来.
在Player下Create Empty,命名ceiling,调整到Player头顶.
修改Player脚本.
1 2 3 4 5 6 7
... public Transform ceilingCheck; ... //下蹲 if(!Physics2D.OverlapCircle(ceilingCheck.position, 0.2f, ground)){//判断Player头有无障碍 ... }
别忘了把ceiling拖到Player的脚本里.
死亡重置:
Create Empty命名DeadLine,拉到地图下面,给它一个标签DeadLine.
修改Player脚本.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
using UnityEngine.SceneManagement; ... private void OnTriggerEnter2D(Collider2D collision) { ... if(collision.tag == "DeadLine") { GetComponent<AudioSource>().enabled = false;//关闭所有音乐 Invoke("Restart", 2f);//延迟两秒执行函数 } ... } void Restart() { SceneManager.LoadScene(SceneManager.GetActiveScene().name);//重载当前场景 } ...
换场景:
新建脚本.
1 2 3 4 5 6 7
using UnityEngine.SceneManagement; ... void Update() { if (Input.GetKeyDown(KeyCode.E)) {//进入当前场景的下一个场景 SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex+1); } }
把这个脚本拖到EnterDalog上去.
2D光源:
素材文件夹里新建Material,将其Shader设置成Sprites/Diffuse,拖给Player的材质用.
新建Light→Point Light,可以放在地图上的火苗位置.
在Player下新建点光源,让Player一直有一点光.
整个场景的光:新建Light→Directional Light.
光是3D的,注意z轴.
视觉差:
就是让不同远近的场景交错跟着摄像机移动.
新建脚本Parallax,我们希望场景跟随相机的速度有倍率关系.
1 2 3 4 5 6 7 8 9
public Transform Cam; public float moveRate; private float startPoint; void Start() { startPoint = transform.position.x; } void Update() { transform.position = new Vector2(startPoint + Cam.position.x * moveRate, transform.position.y); }
这是x轴方向的,还可以添加y轴方向,做Flag来设置哪个方向是需要的.
把Parallax直接拖到BackGround或者其他需要视觉差的.
主菜单
新建Scene:Menu.
新建Panel:background,导入图片(Source Image).
为了让背景是渐变出现,添加动画,录制.
Menu新建脚本.
1 2 3 4 5 6 7 8 9 10 11 12
... using UnityEngine.SceneManagement; ... public void PlayGame() {//开始 SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); } public void QuitGame() {//退出 Application.Quit(); } public void UIEnable() {//UI是否可见 GameObject.Find("Canvas/MainMenu/UI").SetActive(true); }
注意要把Menu这个Scene添加到File→Build Setting里的最上面
Menu下新建两个Button:Play、Quit,设置文字.
让背景渐变结束后才出现Button,Menu动画添加事件,设置函数.
暂停游戏及音量调节
~~~~暂停游戏
首先做一个Pause按钮,在Canvas下新建Button,设置文字Pause.
方便起见直接继续在Menu的脚本里写. 加函数
1 2 3 4 5 6 7 8
public void PauseGame() { pauseMenu.SetActive(true); Time.timeScale = 0f;//时间Scale变0 } public void ContinueGame() { pauseMenu.SetActive(false); Time.timeScale = 1f; }
分别是打开PauseMenu和关闭
Menu的脚本拖给Canvas,再把Canvas拖进Pause按钮的On Click()里,设置函数PauseGame; Continue按钮同理.
注意定位.
~~~~调节音量
Assets里新建Audio Mixer,命名MainMixer.
Menu里添加代码.
1 2 3 4 5 6 7
using UnityEngine.Audio; ... public AudioMixer audioMixer; ... public void SetVolume(float val) { audioMixer.SetFloat("MainVolume", val); }
获得输入的值,将值赋给audioMixer.
正常来说是不支持赋值的,在MainMixer里,点击Master,右键Volume将其设置成可被代码编辑的,同时改一下名字 ,改的名字就是在那个函数里用的字符串.
Canvas,把MainMixer拖进Audio Mixer里.
VolumeSlider的Min Value设置成-80,Max Value设置成0.
可以从下面跳上去的平台
- 这类特殊平台需要创建新Tilemap管理
- 画一个平台,加上Tilemap Collider 2D,勾选Used By Effector
- 加组件Platform Effector 2D,不要勾选Use Collider Mask
更好的管理音效
在熬夜…
总结来说,就是创建了一个空物件,添加脚本,里面写上播放各种音效的函数,设置静态class,将Player上的音效利用这个静态class直接播放.
好困…
优化代码?:
Player脚本换成FixedUpdate(),Time.DeltaTime换成Time.fixedDeltaTime,获取跳跃按键换成GetButton().
改了敌人碰撞部分,在Death()里加.
1
GetComponent<Collider2D>().enabled = false;
直接禁用.
防止一个物件计数两次:这里改的真的莫名其妙的,其实我本来就没有这个问题,但还是记录一下
给Cherry加获得的动画,最后加个事件
Cherry脚本里
1 2 3 4
public void Death() { Destroy(gameObject); FindObjectOfType<PlayerController>().CherryCount(); }
Player脚本里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
... void FixedUpdate(){ ... CherryNum.text = Cherry.ToString();//奇妙的延迟问题 ... } ... private void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Cherry") {//樱桃 collectAudio.Play(); collision.GetComponent<Animator>().Play("isGot");//播放获得动画 } } public void CherryCount() { Cherry++; } ...
Cherry的获得动画那个事件选上Death()
打包游戏
Build…
Day6-8
Day6:
摆烂了两天…主要是不知道怎么开始,一点思路也没有.
不过现在要开始啦😤 *新建文件夹.
今天稍微编了一点设定,画了一点素材.
设定的话,想设计成吸血鬼方向的.(白毛红瞳🤤)
素材,这里留点图吧:
还没画人物,主角想多问问其他人的意见
Day7:
又画了一点素材,进度有点慢(我是真不会画啊艹)
留点图
这张主角草稿主要参考了有一张铃兰的图(铃兰…我的铃兰🤤…我没有啊😭)
Day8:
寄啊,这进度是真滴慢…
今天弄了主角的左右移动,还画了主角寄的动画,然后这一天就过去了…
所幸是我找到背包系统、对话啥的教程在哪了(还是之前那个up有教)
留图:
是我女儿了现在🤤
像素图:
还有些图过于雪芯就不放了…
Day9-11
Day9
今天主要弄了背包,这背包要弄的东西是真的多
背包UI
我这里其实只画了一个这个图就行了
主要是放物件的格子和文本框
先丢个b站教程在这里,我觉得照着做非常舒服BV1YJ41197SU
等我有时间了在看看能不能补一下这里(其实是我自己也没太搞明白)
Day10 Day11
啥也不会,也看不懂…
抄教程的话,抄的云里雾里,而且由于我看的教程比较杂,抄了会有各种各样的问题(
最后还是全靠自己写的(连蒙带猜就胡写出来了
- 切换场景的影流之主BUG
首先切换场景的话,可以看那个教程里写的. 这里我出现了BUG,就是我无法卸载场景,然后会有两个,甚至三个同样的场景同时出现,于是就出现了影流之主的奇妙场面
我把教程里切换场景的码看了好多遍,然后网上也查到了相同的情况,试了很多解决方案都寄了. 最后我到官网手册去查,这一查就查出来了
我的函数是照着教程里写的,先卸载再加载,但我的场景布置和教程不一样. 我的场景是分开的,教程里是在一起的,也就是说我的场景只会同时出现一个,也就不能卸载.
解决方法也就简单了,先加载再卸载即可.
- 警惕背包演人
关于背包最开始也有一点BUG. 虽然我的背包可以永久存储信息,但我的背包UI再第二次打开游戏时是空的,也就是没有把inventory里的信息弄出来,背包存了个寂寞的信息. 我是给inventoryManger加了Start,让它一开始就遍历Bag把东西都加载出来,效果很好.
1
2
3
4
5
6
...
private void Start() {
for (int i = 0; i < instance.Bag.itemList.Count; i++)
CrateNewItem(instance.Bag.itemList[i]);
}
...
- 吃掉的就别吐出来啊
背包还有一个需要解决的问题, 反复切换场景,会发现之前吃掉的物品又被加载出来了. 我在看了各方教程后选择用字典来存储改物件是否被吃掉了,在加载场景时,如果发现该物件已经被收集,就直接Destory. 这部分我写在itemOnWorld(教程里叫slot好像吧)的Awake函数里
1
2
3
4
5
6
7
...
private void Awake() {
if (playerInventory.itemAvailabeDict[itemName]) {
Destroy(gameObject);
}
}
...
这里的playerInventory.itemAvailabeDict即使字典,在inventory里我们加上这个字典:
1
2
3
4
5
6
...
public class inventory : ScriptableObject {
public Dictionary<ItemName, bool> itemAvailabeDict = new Dictionary<ItemName, bool>();
public List<item> itemList = new List<item>();
}
...
Player吃掉物件时将其bool值改一下就行了.
Day12
- Button无效BUG
在做其他场景的时候的出现了一点问题,就是我在其他场景里的背包无法使用Button,网上查到原因是没有加EventSystem.
我对这玩意真是没啥印象,我之前甚至觉得没用想在第一个场景里也给删了…加上就好了
- 翻柜子
我不大想做那种带UI的,甚至有鼠标点击的翻柜子. 我做了个极其简单的. 到柜子附近按”E”就会将柜子下的子物件(即要放在柜子里的item)设置为active. 这里需要找到一个物件的子物件.
1
2
3
4
5
6
7
8
9
...//这里是Player的碰撞检测
switch (collisionNow.tag) {
...
case "Cabinet"://打开柜子,翻出物件
collisionNow.transform.GetChild(0).gameObject.SetActive(true);//将第一个子物件显示
break;
...
}
...
- 判断是否拿到了铲子
设置tag可以轻松解决,但是tag越多,码越丑. 我考虑到有个物品ID的东西就试了一下,开始确实是成功了,然后过一会就寄了,我发现ID变了. 我可以一开始就记录ID,感觉好像和再加一个tag也没啥区别了(
- BUG: 切换场景时Player的信息没了(如已经获得了铲子
在inventoy里记录一下,然后Player的Start里给is_Shovel用inventory里的赋值就行
Day13-14
对于关卡的设计,今天有了一些想法
多个Player,但不在同一地点,Player的移动是互相链接的,每个Player都在一个迷宫里
我是这样实现上面的操作的:给其中一个Player添加多个碰撞体,其余Player跟随这个Main Player移动
需要注意碰撞体的LocalScale也要变
做了迷宫
特点:左右镜像移动,需要左右的Player都到达目的地才能场景互动
没遇到啥bug~留个图
Day15-16
鹰角你坏事(夏活)做尽,摆烂好久…
从迷宫到下一个场景的时候再次出现了影流之主bug,分明之前都解决过的,这又出现了我查了好久没有一点头绪.
最后找到了这一切的罪魁祸首
1
yield return SceneManager.LoadSceneAsync(to, LoadSceneMode.Additive);
这是TransitionManger的部分,最后那个Additive会让场景叠加,改成Single就会自动删除多余的
留个图
后面做了很多天,加了追逐战(用的iwanna的那个)啥的
基本上没啥可加的了(懒的写了)