赞
踩
此篇博客用来记录游戏开发过程中遇到的各种问题以及大致的流程,方便自己以后回顾学习,如果想要了解完整的开发过程,最好去看教学视频。
U
n
i
t
y
Unity
Unity版本:
2019.4.12
f
1
2019.4.12f1
2019.4.12f1。
相应的视频教程请在
B
B
B站搜索
u
p
up
up主
M
_
S
t
u
d
i
o
M\_Studio
M_Studio观看。
在商店中搜索
S
u
n
n
y
L
a
n
d
Sunny\ Land
Sunny Land即可找到对应的资源,下载后导入到当前项目即可。
在编辑背景等图片资源之前有一个知识点需要我们了解,那就是单位格:
上图所示的每一个小方格都是单位格,而图片资源会有如下属性:
通过修改这一参数(单位长度内的像素个数),我们就可以控制图片资源显示的大小。该项目内统一使用
16
16
16。
下面简单介绍一下
T
i
l
e
m
a
p
Tilemap
Tilemap是什么,怎么用。我们可以先看一张图片:
简单来说,利用
T
i
l
e
m
a
p
Tilemap
Tilemap我们可以快速的创建
2
D
2D
2D地图。通过对
S
p
r
i
t
e
Sprite
Sprite也就是图片的切分,我们可以得到一个个
T
i
l
e
Tile
Tile,他们就相当于颜色;我们可以把它们附着在
P
a
l
e
t
t
e
Palette
Palette上面(调色板);然后通过
B
r
u
s
h
Brush
Brush在
T
i
l
e
m
a
p
Tilemap
Tilemap也就是场景中绘制,就像画画一样。那么在
u
n
i
t
y
unity
unity中整个流程是怎么样的呢?
1.
1.
1.切分图片。
2.
2.
2.创建
P
a
l
e
t
t
e
Palette
Palette。
3.
3.
3.将
T
i
l
e
Tile
Tile添加到
P
a
l
e
t
t
e
Palette
Palette中(拖入图片创建
T
i
l
e
Tile
Tile)。
4.
4.
4.在场景中创建
T
i
l
e
m
a
p
Tilemap
Tilemap。
5.
5.
5.通过
B
r
u
s
h
Brush
Brush把
T
i
l
e
Tile
Tile绘制到
T
i
l
e
m
a
p
Tilemap
Tilemap中。
如果在
g
a
m
e
game
game视图下看到场景中有缝隙,可以修改这个参数:
有时候我们需要控制场景中不同物体的渲染顺序,比如地形之类的应该在背景之上显示,这个该怎么做呢?
S
o
r
t
i
n
g
L
a
y
e
r
、
O
r
d
e
r
i
n
L
a
y
e
r
Sorting\ Layer、Order\ in\ Layer
Sorting Layer、Order in Layer。前者选中的
l
a
y
e
r
layer
layer越靠下,显示的越靠上;同一
l
a
y
e
r
layer
layer中,后者的值越大,显示的越靠上。
想要创建人物,首先要在场景中创建一个精灵,然后修改它所引用的图片。同时不要忘了修改位置、层级以及图片的大小(
P
i
x
e
l
s
p
e
r
u
n
i
t
Pixels\ per\ unit
Pixels per unit)。
但是此时我们的人物还不会动,也不受重力影响。所以我们需要为其加上
r
i
g
i
d
b
o
d
y
2
D
rigidbody2D
rigidbody2D和碰撞体组件,当然,场景中的地形也需要加上碰撞体。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody2D rb2d; public float speed = 5f; // Start is called before the first frame update void Start() { rb2d = GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { Movement(); } void Movement() { float horizontalMove = Input.GetAxis("Horizontal"); rb2d.velocity = new Vector2(horizontalMove * speed, rb2d.velocity.y); } }
上述代码即可实现简单的人物移动,但是在测试中你可能会发现人物会出现摔倒等现象,这是由于发生碰撞时人物绕
z
z
z轴旋转了,所以我们可以冻结这一旋转:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody2D rb2d; public float speed = 5f; public float jumpForce = 5f; // Start is called before the first frame update void Start() { rb2d = GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { Movement(); } void Movement() { float horizontalMove = Input.GetAxis("Horizontal"); int faceDirection = (int)Input.GetAxisRaw("Horizontal"); rb2d.velocity = new Vector2(horizontalMove * speed, rb2d.velocity.y); if (faceDirection != 0) { transform.localScale = new Vector3(faceDirection, 1, 1); } if (Input.GetButtonDown("Jump")) { rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce); } } }
上述代码实现了人物的翻转、跳跃。翻转的做法有很多种,除了修改
s
c
a
l
e
scale
scale还可以通过修改这面这个属性做到:
但是这两种方法不是一直都等价的,考虑人物的背上还有装备的情况,那么一般情况下它们是父子关系,此时修改父物体的
s
c
a
l
e
scale
scale,子物体也会一同修改,但是上面这个
F
l
i
p
Flip
Flip是独立的,所以这种情况下想要实现翻转的话应该利用
s
c
a
l
e
scale
scale。
由于我们还没有做地面检测,所以人物可以一直跳。
给人物添加 A n i m a t o r Animator Animator组件之后就可以开始制作动画了~
通过这个设置可以查看当前动画的帧率并进行设置。
按住左键不松可以选取一定区域内的所有关键帧,或者先选中一个关键帧,此时按住
s
h
i
f
t
shift
shift不松再选择另一个关键帧,即可自动选中它们之间的所有关键帧(
u
n
i
t
y
unity
unity中选择多个文件的方法同理),此时再拖动(选中区域的两侧有竖线标识)即可均匀的设置间隔而无需一个一个的调整。
在制作好
i
d
l
e
idle
idle和
r
u
n
run
run两段动画后,就可以开始修改
a
n
i
m
a
t
o
r
animator
animator了。我们需要为这两段动画之间加一个过渡条件:
若勾选
H
a
s
E
x
i
t
T
i
m
e
Has\ Exit\ Time
Has Exit Time,则代表从动画
A
A
A到动画
B
B
B有特定的退出(转换)时间,具体值由
E
x
i
t
T
i
m
e
Exit\ Time
Exit Time决定,也就是说切换可能会有延迟(不能立即切换);
T
r
a
n
s
i
t
i
o
n
D
u
r
a
t
i
o
n
Transition\ Duration
Transition Duration决定了过渡的时间,即动作
A
A
A经过多久可以过渡到动作
B
B
B。这里,我们既不需要退出时间,也不需要过渡时间。代码只需要改动一点点,修改
r
u
n
n
i
n
g
running
running的值即可。
void Movement()
{
animator.SetFloat("running", Mathf.Abs(horizontalMove));
}
跳跃动画需要分成两部分:
1.
1.
1.起跳;
2.
2.
2.下落,所以我们需要制作两个动画,然后修改状态机:
现在考虑代码怎么改。起跳是很简单的,只需要在用户按下空格键时,将
j
u
m
p
i
n
g
jumping
jumping设为真;降落也比较简单,当人物在跳跃状态且
y
y
y方向的速度
<
0
<0
<0时,说明人物开始降落了,那么此时应将
j
u
m
p
i
n
g
jumping
jumping设为假,
f
a
l
l
i
n
g
falling
falling设为真;关键是如何判断人物落地了呢?通过碰撞来判断。在这里我们引入
L
a
y
e
r
M
a
s
k
LayerMask
LayerMask,它在射线检测中经常被用到,用来实现与特定层的检测(忽略其它层)。那么我们自然想到可以将地面的
l
a
y
e
r
layer
layer设置为
g
r
o
u
n
d
ground
ground,然后在代码中判断人物的碰撞体是否与
g
r
o
u
n
d
ground
ground接触即可:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { private Rigidbody2D rb2d; private Animator animator; private BoxCollider2D boxCollider2D; public float speed = 5f; public float jumpForce = 5f; public LayerMask ground; // Start is called before the first frame update void Start() { rb2d = GetComponent<Rigidbody2D>(); animator = GetComponent<Animator>(); boxCollider2D = GetComponent<BoxCollider2D>(); } // Update is called once per frame void Update() { Movement(); SwitchAnimation(); } void Movement() { float horizontalMove = Input.GetAxis("Horizontal"); int faceDirection = (int)Input.GetAxisRaw("Horizontal"); rb2d.velocity = new Vector2(horizontalMove * speed, rb2d.velocity.y); animator.SetFloat("running", Mathf.Abs(horizontalMove)); if (faceDirection != 0) { transform.localScale = new Vector3(faceDirection, 1, 1); } if (Input.GetButtonDown("Jump")) { rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce); animator.SetBool("jumping", true); } } void SwitchAnimation() { //animator.SetBool("idle", false); if (animator.GetBool("jumping")) //跳跃状态 { if (rb2d.velocity.y < 0) { animator.SetBool("jumping", false); animator.SetBool("falling", true); } } else if (animator.GetBool("falling")) //下落状态 { if (boxCollider2D.IsTouchingLayers(ground)) { animator.SetBool("falling", false); animator.SetBool("idle", true); } } } }
上述代码实现了人物各种动作之间的切换。你可能注意到了上述代码中的一些问题,当 i d l e idle idle被设为真后,就一直为真,但是这并不影响动作的切换,因为到目前为止 i d l e idle idle其实没有存在的必要,但是为了与教程统一,我还是加入了这个变量。所以大家不要盲目的相信教程全都是正确的,从教程中学到方法,然后自己思考怎么做。
人物在移动时可能出现卡住不动的情况,这个应该是碰撞体的问题。在
s
c
e
n
e
scene
scene视图下我们可以看到当前地形(
T
i
l
e
m
a
p
Tilemap
Tilemap)的碰撞体:
可以发现每一个方格都有自己的碰撞体,然而很多地方是没有必要的,比如地形的内部。所以我们需要使用
c
o
m
p
o
s
i
t
e
c
o
l
l
i
d
e
r
composite\ collider
composite collider来把这些碰撞体合并到一起,具体做法就是为
t
i
l
e
m
a
p
tilemap
tilemap添加一个对应的组件:
可以看到地形内部的碰撞体消失了(被合并成了一个整体),这样做不仅可以减少计算量,提高性能,还可以解决人物移动时突然卡住的
b
u
g
bug
bug。但是如果这个时候运行游戏,你会发现地形和人物一起掉落了,这是因为
c
o
m
p
o
s
i
t
e
c
o
l
l
i
d
e
r
2
D
composite\ collider\ 2D
composite collider 2D需要
r
i
g
i
d
b
o
d
y
2
D
rigidbody\ 2D
rigidbody 2D,所以地形也会受到重力影响,这个问题怎么解决呢?首先我们来了解一下
r
i
g
i
d
b
o
d
y
2
D
rigidbody\ 2D
rigidbody 2D的
b
o
d
y
t
y
p
e
body\ type
body type。
显然,我们是不希望地形进行移动的,所以应该选取
s
t
a
t
i
c
static
static。
C
i
n
e
m
a
c
h
i
n
e
Cinemachine
Cinemachine是一个非常棒的插件,利用它可以快速制作出满足我们需求的摄像机——跟随人物缓慢移动。首先我们需要把背景拉长,多复制几份即可:
注意这里有一个小技巧,在利用移动工具移动游戏对象时,按住
V
V
V会在鼠标附近的区域出现如上图所示的白色方框,拖动白色方框移动游戏对象则会使它附着在附近的其它游戏对象上,也就是说可以自动进行对齐等操作,更加方便。
修改完背景后,就可以添加
C
i
n
e
m
a
c
h
i
n
e
Cinemachine
Cinemachine摄像机了,并且让它跟随我们的人物。
从上图可以看到这个摄像机分为三个区域,人物在最中间的区域移动时,摄像机不会跟随;在浅蓝色区域移动时,摄像机缓慢跟随;在红色区域移动时,摄像机快速跟随。右边红框内的参数可以调节这几个区域的大小。现在还有一个问题就是,当相机随着人物移动到边界时,会看到超出背景的部分,这是我们不希望的。但是这个摄像机已经为我们考虑到了:
我们只需要给背景增加一个
p
o
l
y
g
o
n
c
o
l
l
i
d
e
r
2
D
polygon\ collider\ 2D
polygon collider 2D(编辑这种碰撞体时,按住
c
t
r
l
ctrl
ctrl可以删除某一个点),然后把它拖到上面的
B
o
u
n
d
i
n
g
Bounding
Bounding即可。注意把背景的碰撞体设置成触发器,不然会把人物顶出去。
仿照人物的制作方式,自己动手制作樱桃,并修改其
t
a
g
tag
tag:
修改代码使得人物碰到樱桃时,樱桃消失(之前写的代码省略掉了):
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { [SerializeField] private int cherry = 0; private void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Collection") { Destroy(collision.gameObject); ++cherry; } } }
新建
P
r
e
f
a
b
Prefab
Prefab文件夹,把人物和樱桃都设置为预制体:
接下来大家可以自己发挥,把场景设置的更加丰富~
相信大家在游玩的过程中可能已经发现了一些问题:
1.
1.
1.人物碰到某个物体后如果一直按着方向键,那么人物不会落下,会卡在物体上;
2.
2.
2.人物可以无限跳跃。现在我们就来解决这些问题。
首先来解决第一个问题,为什么按着方向键就会卡住,不按就可以正常落下呢?因为摩擦力(真实的物理引擎 )。所以我们要做的就是修改人物碰撞体的材质:
然后来解决第二个问题,只要在代码中进行控制,当人物位于地面上时才允许跳跃就行了:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
void Movement()
{
if (Input.GetButtonDown("Jump") && boxCollider2D.IsTouchingLayers(ground))
{
rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce);
animator.SetBool("jumping", true);
}
}
不过此时人物依然可以蹬墙跳。
想让界面显示出任务获得的樱桃个数,那么就需要用到
U
I
UI
UI了:
图片和分数我们都想放到屏幕左上角,那么应该通过设置锚点来定位。如果单单使用到中心点的偏移的话,当屏幕大小发生变化时,
U
I
UI
UI极有可能出现在错误的位置。
分数的控制由代码实现:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { public Text cherryText; private void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Collection") { Destroy(collision.gameObject); ++cherry; cherryText.text = cherry.ToString(); } } }
首先仿照人物的制作方法制作出一个青蛙敌人,然后将其设为预制体:
青蛙的碰撞体不能当作触发器使用,因为它有
r
i
g
i
d
b
o
d
y
2
D
rigidbody\ 2D
rigidbody 2D组件,当成触发器的话会直接掉落。我们希望人物跳起来落到青蛙头上时可以消灭它,同时有一个小跳的效果:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "Enemy" && animator.GetBool("falling")) { Destroy(collision.gameObject); rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce); //小跳效果 animator.SetBool("jumping", true); } } }
不过这里还是存在一些问题的,由于当前的动画设置,人物从高处落下时并不一定处于降落状态,可以考虑把这里的判断条件改为人物的 v e l o c i t y . y < 0 velocity.y<0 velocity.y<0。
首先按照教程把修改代码如下所示:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { [SerializeField] private bool isHurt = false; // Update is called once per frame void Update() { if (!isHurt)//非受伤状态 { Movement(); } SwitchAnimation(); } void SwitchAnimation() { //animator.SetBool("idle", false); if (animator.GetBool("jumping")) //跳跃状态 { if (rb2d.velocity.y < 0) { animator.SetBool("jumping", false); animator.SetBool("falling", true); } } else if (animator.GetBool("falling")) //下落状态 { if (boxCollider2D.IsTouchingLayers(ground)) { animator.SetBool("falling", false); animator.SetBool("idle", true); } } else if (isHurt) //受伤状态 { if (Mathf.Abs(rb2d.velocity.x) < 0.1f)//速度过小时认为回到正常状态 { isHurt = false; } } } private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "Enemy") { if (animator.GetBool("falling")) //掉落状态 { Destroy(collision.gameObject); rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce); //小跳效果 animator.SetBool("jumping", true); } else if (transform.position.x < collision.gameObject.transform.position.x)//左侧 { isHurt = true; rb2d.velocity = new Vector2(-10f, rb2d.velocity.y); } else if(transform.position.x > collision.gameObject.transform.position.x)//右侧 { isHurt = true; rb2d.velocity = new Vector2(10f, rb2d.velocity.y); } } } }
但是在运行后我发现了一个问题,就是碰撞后人物会一直后退直到碰到其它碰撞体。这是因为教程中使用了两个碰撞体,而我只使用了一个而且把摩擦力改成了 0 0 0,所以我决定通过代码控制受伤状态的速度:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { [SerializeField] private bool isHurt = false; // Update is called once per frame void Update() { if (!isHurt)//非受伤状态 { Movement(); } SwitchAnimation(); } void SwitchAnimation() { //animator.SetBool("idle", false); if (animator.GetBool("jumping")) //跳跃状态 { if (rb2d.velocity.y < 0) { animator.SetBool("jumping", false); animator.SetBool("falling", true); } } else if (animator.GetBool("falling")) //下落状态 { if (boxCollider2D.IsTouchingLayers(ground)) { animator.SetBool("falling", false); animator.SetBool("idle", true); } } else if (isHurt) //受伤状态 { int sign = rb2d.velocity.x < 0 ? -1 : 1; rb2d.velocity += new Vector2(speed * Time.deltaTime, 0f) * -sign; if (Mathf.Abs(rb2d.velocity.x) < 0.1f) { isHurt = false; } } } private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.tag == "Enemy") { if (animator.GetBool("falling")) //掉落状态 { Destroy(collision.gameObject); rb2d.velocity = new Vector2(rb2d.velocity.x, jumpForce); //小跳效果 animator.SetBool("jumping", true); } else if (transform.position.x < collision.gameObject.transform.position.x)//左侧 { isHurt = true; rb2d.velocity = new Vector2(-5f, rb2d.velocity.y); } else if(transform.position.x > collision.gameObject.transform.position.x)//右侧 { isHurt = true; rb2d.velocity = new Vector2(5f, rb2d.velocity.y); } } } }
下一步就是制作受伤时的动画了:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerController : MonoBehaviour { void SwitchAnimation() { //animator.SetBool("idle", false); if (animator.GetBool("jumping")) //跳跃状态 { if (rb2d.velocity.y < 0) { animator.SetBool("jumping", false); animator.SetBool("falling", true); } } else if (animator.GetBool("falling")) //下落状态 { if (boxCollider2D.IsTouchingLayers(ground)) { animator.SetBool("falling", false); animator.SetBool("idle", true); } } else if (isHurt) //受伤状态 { animator.SetBool("hurt", true); int sign = rb2d.velocity.x < 0 ? -1 : 1; rb2d.velocity += new Vector2(speed * Time.deltaTime, 0f) * -sign; if (Mathf.Abs(rb2d.velocity.x) < 0.1f) { isHurt = false; animator.SetBool("hurt", false); } } }
这里教程采用的方法是在青蛙左右两侧设置两个点,把青蛙的移动区域固定到这两个点内。我这里没有采用这种做法,而是使用了 P h y s i c s 2 D . L i n e c a s t Physics2D.Linecast Physics2D.Linecast判断青蛙前方是否是空地,更具体一点就是判断青蛙脚下是不是地面,以及青蛙头上有没有障碍物:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy_Frog : MonoBehaviour { private Rigidbody2D rb2d; public LayerMask ground; public float speed = 5f; [SerializeField] private bool faceLeft = true; // Start is called before the first frame update void Start() { rb2d = GetComponent<Rigidbody2D>(); } // Update is called once per frame void Update() { Movement(); } void Movement() { Vector2 frontPosition = transform.position; if (faceLeft) frontPosition += Vector2.left; else frontPosition += Vector2.right; if (!Physics2D.Linecast(frontPosition + Vector2.down, frontPosition, ground) || Physics2D.Linecast(frontPosition, frontPosition + Vector2.up, ground)) //没有检测到地面 或者头上有障碍物 { faceLeft = !faceLeft; transform.localScale = new Vector3(faceLeft ? 1 : -1, 1, 1); //角色反向 } rb2d.velocity = Vector2.right * (faceLeft ? -speed : speed); } private void OnDrawGizmos() //debug用 { Vector2 frontPosition = transform.position; if (faceLeft) frontPosition += Vector2.left; else frontPosition += Vector2.right; Gizmos.DrawLine(frontPosition + Vector2.down, frontPosition + Vector2.up); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。