开发准备

核心玩法

  • 小人跳跃的距离和你按压屏幕的时长有关,按屏幕时间越长,跳的越远。
  • 跳到盒子上可以加分,没跳到盒子上游戏结束。
  • 连续跳到盒子中心可以成倍加分。
  • 联网查询排行榜和上传分数。

功能需求分析

  • 角色跳跃
  • 相机跟随
  • 台子自动生成
  • 死亡判定及重新开始游戏
  • 分数统计
  • 色蓄力的粒子效果
  • 角色蓄力效果
  • 角色蓄力台子效果
  • 飘分效果
  • 联网排行榜功能
  • 翻倍加分功能

开发文档

创建游戏场景

  1. 创建一个Plane当地面,并重命名为Ground

  1. 创建一个材质球改变Ground颜色(默认Plane控件是不能设置材质颜色),创建一个Materials文件夹放入材质球

  1. 创建一个Cube,y轴缩放0.5当跳板,创建物体可以通过TranssformReset设置为3D场景正中心,在创建一个Plane当地面,跳一跳小人物碰到地面时游戏借宿,为了让跳板出现地面上方,设置y轴Postion为0.25(Reset设置居中是以组件正中心)

  1. 创建一个空物体重命名为Player作为玩家角色
    • Cylinder(圆柱形)控件作为玩家身体
    • Sphere(圆形)控件作为玩家头部
    • 调整至合适的大小、位置
    • 添加材质球,将材质球绑定到Cylinder(圆柱形)控件和Sphere(圆形)控件上,改变材质球颜色,材质球放入Materials文件夹

  1. 保存场景

角色跳跃

  1. 添加游戏脚本Player放入新创建Scripts文件夹,并将脚本绑定到Player控件上,并在Player控件上添加一个物理组件Rigidbody

  1. 获得Rigidbody组件
1
2
3
4
5
private Rigidbody _rigidbody;

void Start () {
_rigidbody = GetComponent<Rigidbody>();
}
  1. 计算出按下鼠标和松开鼠标之间的时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private float _startTime;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
//按下按键的时间
_startTime = Time.time;
}

if (Input.GetMouseButtonUp(0))
{
// 计算总共按下空格的时长
var elapse = Time.time - _startTime;
}
}
  1. Player跳跃方法的实现
1
2
3
4
5
6
7
// 小人跳跃时,决定远近的一个参数
public float Factor = 2;
void OnJump(float elapse)
{
//给刚体增加力
_rigidbody.AddForce(new Vector3(1, 5f, 0) * elapse * Factor, ForceMode.Impulse);
}

  1. 发现人物跳跃落地时会出现跌倒,这是因为重心和碰撞盒的问题
1
2
3
4
5
6
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
//设置质量中心
_rigidbody.centerOfMass = new Vector3(0, 0, 0);
}
  1. 将Body下的Capsle Collider去掉,替换成Box Collider

  1. 修改当前视角,选中Main Camera按下快捷键Ctrl+Alt+F  (GameObject->Align With->View)

  1. 修改Factoer为5,测试(这个盒子是我自己复制Stage出来的)

测试

自动生成盒子

  1. 随机生成盒子位置
1
stage.transform.position = _currentStage.transform.position + new Vector3(Random.Range(1.1f,MaxDistance),0,0);
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
// 小人跳跃时,决定远近的一个参数
public float Factor = 5;

// 第一个盒子物体
public GameObject Stage;

// 盒子随机最远的距离
public float MaxDistance = 3;

private Rigidbody _rigidbody;
private float _startTime;
private GameObject _currentStage;

void Start()
{
_rigidbody = GetComponent<Rigidbody>();
//设置质量中心
_rigidbody.centerOfMass = new Vector3(0, 0, 0);
}

private void SpawnStage()
{
var stage = Instantiate(Stage);
stage.transform.position = _currentStage.transform.position + new Vector3(Random.Range(1.1f, MaxDistance), 0, 0);
}
  1. 跳到一个盒子上再随机生成一个盒子
    • 如果有别的物体和本物体发生变化,会触发这函数OnCollisionEnter(Collision collision)
    • 轻轻一跳在同一个盒子上时,不能在重新生成新的盒子
    • 没有跳到下一个盒子上,不能再生成新的盒子
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

void Start () {
_rigidbody = GetComponent<Rigidbody>();
//修改物理组件的重心到body的底部
_rigidbody.centerOfMass = Vector3.zero;

_currentStage = Stage;
_lastCollisionCollider = _currentStage.GetComponent<Collider>();
SpawnStage();
}

/// <summary>
/// 小人刚体与其他物体发生碰撞时自动调用
/// </summary>
void OnCollisionEnter(Collision collision)
{
Debug.Log(collision.gameObject.name);

if (collision.gameObject.name.Contains("Stage") && collision.collider != _lastCollisionCollider)
{
_lastCollisionCollider = collision.collider;
_currentStage = collision.gameObject;
SpawnStage();
}
}

相机跟随

  1. 获得主相机位置
1
Camera.main.transform.position
  1. 获得相机下一次的相对位置
1
public Vector3 _camerRelativePosition; 
  1. 获得相机移动的距离
1
2
//获得相机移动的距离
_cameraRelativePosition = Camera.main.transform.position - transform.position;
  1. 使用DG.Tweening插件(没有的话请去资源商店下载)(Window->Package Manager)

  1. 添加移动相机的动画,并在OnCollisionEnter中调用
1
2
3
4
void MoveCamera()
{
Camera.DOMove(transform.position + _camerRelativePosition,1);
}
  1. 修改Ground的Scale为(1000,1,1000),进行测试

死亡判定及重新开始

  1. 小人落到地面上就可以判断游戏结束了 

  2. 当Player碰到地面时,重新加载场景

1
2
3
4
5
if (collision.gameObject.name == "Ground")
{
//本局游戏结束,重新开始
SceneManager.LoadScene(0);
}
  1. 要注意光源问题,光源保存在缓存中,重新加载场景时不会加载光照,生成静态光照

分数及UI显示

  1. 创建一个Text文本控件命名为TotalScore,并设置2D文本位置,右上角设置文字位置

  1. 当跳到新的方块上时,分数增加
1
2
3
4
5
6
7
8
9
10
if (collision.gameObject.name.Contains("Stage") && collision.collider != _lastCollisionCollider)
{
_lastCollisionCollider = collision.collider;
_currentStage = collision.gameObject;
SpawnStage();
MoveCamera();

_score++;
TotalScoreText.text = _score.ToString();
}

角色蓄力粒子效果

  1. 在Player上创建粒子系统Particle System

  2. 将粒子系统Shape中的Shaper改为Hemisphere圆形发射,并修改Potation上的X值为-90并修改粒子系统的初始值

  1. 修改脚本,当角色蓄力的时候才会显现出粒子效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
public GameObject Particle;

Particle = GameObject.Find("yellow");
Particle.SetActive(false);

if (Input.GetMouseButtonDown(0))
{
_stateTime = Time.time;
Particle.SetActive(true);
}
if (Input.GetMouseButtonUp(0))
{
var elapse = Time.time - _stateTime;
OnJump(elapse);
Particle.SetActive(false);
}

角色蓄力动画

  1. 当按下鼠标键时,小人物的身体进行缩放,我们可以通过脚本来设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (Input.GetMouseButtonUp(0))
{
// 计算总共按下空格的时长
var elapse = Time.time - _startTime;
//跳跃事件
OnJump(elapse);

//抬起鼠标关闭粒子效果
Particle.SetActive(false);

//还原小人的形状
Body.transform.DOScale(0.1f, 0.2f);
Head.transform.DOLocalMoveY(0.29f, 0.2f);
}

if (Input.GetMouseButton(0))
{
//身体 局部缩放
Body.transform.localScale += new Vector3(1, -1, 1) * 0.05f * Time.deltaTime;
//头部 局部缩放
Head.transform.localPosition += new Vector3(0, -1, 0) * 0.1f * Time.deltaTime;
}

盒子蓄力动画

  1. 盒子缩放沿着轴心缩放
  2. 盒子恢复形状
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
if (Input.GetMouseButtonUp(0))
{
// 计算总共按下空格的时长
var elapse = Time.time - _startTime;
//跳跃事件
OnJump(elapse);

//抬起鼠标关闭粒子效果
Particle.SetActive(false);

//还原小人的形状
_currentStage.transform.DOLocalMoveY(0.25f, 0.2f);
_currentStage.transform.DOScale(new Vector3(1, 0.5f, 1), 0.2f);
//还原盒子的形状
_currentStage.transform.DOLocalMoveY(0.25f, 0.2f);
_currentStage.transform.DOScale(new Vector3(1, 0.5f, 1), 0.2f);
}

if (Input.GetMouseButton(0))
{
//身体 局部缩放
Body.transform.localScale += new Vector3(1, -1, 1) * 0.05f * Time.deltaTime;
//头部 局部缩放
Head.transform.localPosition += new Vector3(0, -1, 0) * 0.1f * Time.deltaTime;
//盒子沿轴心缩放
_currentStage.transform.localScale += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;
_currentStage.transform.localPosition += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;
}

盒子随机大小及颜色

  1. 随机大小
  2. 随机颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void SpawnStage()
{
//生成台子
var stage = Instantiate(Stage);
stage.transform.position = _currentStage.transform.position + new Vector3(Random.Range(1.1f, MaxDistance), 0, 0);

//随机大小
var randomScale = Random.Range(0.5f, 1);
stage.transform.localScale = new Vector3(randomScale, 0.5f, randomScale);

//随机颜色
stage.GetComponent<Renderer>().material.color =
new Color(Random.Range(0f, 1), Random.Range(0f, 1), Random.Range(0f, 1));
}

盒子随机方向生成

  1. 初始的时候设置生成的方向是沿X轴正方向
1
Vector3 _direction = new Vector3(1, 0, 0);
  1. 随机生成跳台(沿X轴或Z轴)
1
2
3
4
5
6
7
8
9
10
11
12
13
  
void RandomDirection()
{
var seed = Random.Range(0,2);
if(seed == 0)
{
_direction = new Vector3(1, 0, 0);
}
else
{
_direction = new Vector3(0,0,1);
}
}
  1. 改变小人物跳跃方向
1
2
3
4
5
6
7
  
void OnJump(float elapse)
{
//给刚体增加力
//_rigidbody.AddForce(new Vector3(1, 5f, 0) * elapse * Factor, ForceMode.Impulse);
_rigidbody.AddForce((new Vector3(0,1,0)+_direction)*elapse* Factoer,ForceMode.Impulse);
}
  1. 增加台子生成方向参数
1
2
3
4
//生成台子
var stage = Instantiate(Stage);
// stage.transform.position = _currentStage.transform.position + new Vector3(Random.Range(1.1f, MaxDistance), 0, 0);
stage.transform.position = _currentStage.transform.position + _direction * Random.Range(1.1f, MaxDistance);

增加角色跳跃动画

  1. 添加动画
1
2
3
4
5
6
7
8
void OnJump(float elapse)
{
//给刚体增加力
//_rigidbody.AddForce(new Vector3(1, 5f, 0) * elapse * Factor, ForceMode.Impulse);
_rigidbody.AddForce(new Vector3(0, 5f, 0) + (_direction) * elapse * Factor, ForceMode.Impulse);
//旋转动画
transform.DOLocalRotate(new Vector3(0, 0, -360), 0.6f, RotateMode.LocalAxisAdd);
}

优化盒子相关事件

  1. 防止过度缩放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 处理按下空格时小人和盒子的动画
if (Input.GetMouseButton(0))
{
//添加限定,盒子最多缩放一半
if (_currentStage.transform.localScale.y > 0.3)
{
//身体 局部缩放
Body.transform.localScale += new Vector3(1, -1, 1) * 0.05f * Time.deltaTime;
//头部 局部缩放
Head.transform.localPosition += new Vector3(0, -1, 0) * 0.1f * Time.deltaTime;

//盒子沿轴心缩放
_currentStage.transform.localScale += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;
_currentStage.transform.localPosition += new Vector3(0, -1, 0) * 0.15f * Time.deltaTime;
}
}
  1. 修复盒子缩放后会还原为预设体大小
1
2
3
4
//还原盒子的形状
_currentStage.transform.DOLocalMoveY(0.25f, 0.2f);
//_currentStage.transform.DOScale(new Vector3(1, 0.5f, 1), 0.2f);
_currentStage.transform.DOScaleY(0.5f, 0.2f);

随机生成不同盒子

  1. 创建一个圆柱体并且重命名为StageCylinder,放入Prefabs文件夹

  1. 创建一个盒子仓库
1
2
// 盒子仓库,可以放上各种盒子的prefab,用于动态生成。
public GameObject[] BoxTemplates;
  1. 随机生成
1
2
3
4
5
6
7
8
9
10
GameObject prefab;
if (BoxTemplates.Length > 0)
{
// 从盒子库中随机取盒子进行动态生成
prefab = BoxTemplates[Random.Range(0, BoxTemplates.Length)];
}
else
{
prefab = StageBox;
}

联网排行榜(api)

  1. LeanCloud官网: https://www.leancloud.cn/
  2. LeanCloud国际版: https://leancloud.app/
  3. SDK下载 :https://releases.leanapp.cn/#/leancloud/unity-sdk/releases
  4. 通过Unity的Http通讯进行数据的增删改查