unity开发微信跳一跳
目录
建立项目
准备人物、地板、小方块
实现摄像头跟随
实现地板移动
实现小方块自动生成
实现小人跳动
实现小人伸缩
实现小方块自动销毁
实现小方块随机大小和随机颜色
实现分数显示
实现跌落重玩
player完整脚本
建立项目
简单实现一下微信跳一跳小游戏,打包成安卓安装包放手机上玩。
首先建一个3D空项目,要记住项目名称不能有中文、空格、特殊字符(别问我是怎么知道的T_T)。
准备人物、地板、小方块
然后先建个小人出来,弄个player,给他两个子对象,一个球体当头,一个圆柱体当身子。
可以搞个材料上色,没有颜色也没关系,我一开始先不管,颜色是后面添加材料有的。
再整块Plane当地板,一定要足够大。
最后弄个cube小方块当平台让小人站在上面。
然后我们的东西就基本准备好了。
实现摄像头跟随
我们的主人公是会跳的,所以我们必须让我们的摄像头跟着小人一起跑。
首先手动给摄像头寻找最好的参数视角:
然后写个脚本挂在摄像头上,就让它以我们刚刚找好的偏移时刻跟着我们的player跑。
using UnityEngine;
public class follow_player : MonoBehaviour
{
public Transform player;
public Vector3 offset;
private void FixedUpdate()
{
transform.position = player.position + offset;
}
}
记得把我们的player拉到这里,还有设置好偏移:
实现地板移动
同样的道理,我们的地板也要跟着小人一起移动,为什么呢?因为地板是有大小限制的,小人不停的跑是有可能会跑出边界的,所以我们要让地板一起走。
给地板挂个脚本:
using UnityEngine;
public class Ground : MonoBehaviour
{
public Transform player;
// Update is called once per frame
void Update()
{
var position = player.position;
transform.position = new Vector3(position.x, 0, position.z);
}
}
有同学可能会问了,那地板跑了,小方块不也跑了?
有道理,但是不给他摩擦力他怎么跑@_@。
实现小方块自动生成
我们要让小人跳到一块小方块就自动生成下一块小方块,写个脚本,为了方便后面脚本都统一挂在小人身上,除了小方块的随机大小和颜色,这个我们后面再说。
我们写一个自动生成小方块的函数,当然在此之前我们先把小方块拖进Asset里面整成预制件。
由于我们的跳一跳是只有两个方向的,所以我们先随机一个方向出来,然后分情况生成,对于位置,我们需要记录前一个小方块的位置,然后在此之上做偏移。
private void NewCube()
{
var random = new System.Random();
direction = random.Next(0, 2);
if (direction == 0)
{
var cube = Instantiate(cubePrefab, new Vector3(Random.Range(10, 13), 0, 0) + cubePlace,
Quaternion.identity);
cubePlace = cube.transform.position;
cubes.Enqueue(cube);
}
else
{
var cube = Instantiate(cubePrefab, new Vector3(0, 0, Random.Range(10, 13)) + cubePlace,
Quaternion.identity);
cubePlace = cube.transform.position;
cubes.Enqueue(cube);
}
if (cubes.Count > maxCubeNumber)
{
var cube = cubes.Dequeue();
Destroy(cube);
}
}
当然你会发现这里多了个队列,这个我们后面销毁小方块会用到。
实现小人跳动
这里是最关键的地方,也是bug最多的地方。
首先简单来说,我们需要记录触摸手机屏幕的时间作为小人冲量的大小。
根据二八原则,80%的代码是为了解决bug出现的,20%的代码就可实现简单功能。
在这里我们只讲那实现简单功能的20%代码,剩下的80%靠大家领悟。
var touch = Input.touches[0];
if (touch.phase == TouchPhase.Began)
{
start = Time.time;
}
else if (touch.phase == TouchPhase.Ended)
{
var time = Time.time - start;
if (direction == 0)
{
playerRigidbody.AddForce(new Vector3(1, 1, 0) * (time * speed), ForceMode.Impulse);
}
else
{
playerRigidbody.AddForce(new Vector3(0, 1, 1) * (time * speed), ForceMode.Impulse);
}
}
到此,本游戏基本功能基本实现,下面是继续完善版。
实现小人伸缩
我要的效果就是我摁下去他就变矮变胖。
同时矮了一半了就不能再矮了,跳也不能跳太远,至于不能跳太近是因为那样会引发某个bug,我的解决办法就是当无法解决bug的时候就去禁止导致bug的行为,还有就是在空中飞的时候不能再跳了。
在原来代码的基础上丰富:
if (Input.touchCount > 0 && inTheAir == false)
{
jump = true;
if (body.transform.localScale.y > 0.5)
{
body.transform.localScale += new Vector3(1, -1, 1) * (0.45f * Time.deltaTime);
head.transform.localPosition += new Vector3(0, -1, 0) * (0.45f * Time.deltaTime);
}
var touch = Input.touches[0];
if (touch.phase == TouchPhase.Began)
{
start = Time.time;
}
else if (touch.phase == TouchPhase.Ended)
{
var time = Time.time - start;
if (time > 3)
{
time = 3;
}else if (time < 0.5f)
{
time = 0.5f;
}
if (direction == 0)
{
playerRigidbody.AddForce(new Vector3(1, 1, 0) * (time * speed), ForceMode.Impulse);
}
else
{
playerRigidbody.AddForce(new Vector3(0, 1, 1) * (time * speed), ForceMode.Impulse);
}
body.transform.localScale = new Vector3(1, 1.5f, 1);
head.transform.localPosition = new Vector3(0, 3.7f, 0);
inTheAir = true;
}
}
实现小方块自动销毁
用过了的就没有利用价值,特别是在视野范围内都看不到你的时候。
我们通过维持一个小方块队列,当队列数量超过一定数目时,销毁队首小方块。
if (cubes.Count > maxCubeNumber)
{
var cube = cubes.Dequeue();
Destroy(cube);
}
后期测试的时候发现一个问题,那就是会把第一个预制件销毁,导致后面无法生成小方块,因此需要解脱第一个小方块预制件的身份,让它成为一个普通的克隆件。
实现小方块随机大小和随机颜色
给它挂个小脚本:
using UnityEngine;
public class Cube : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GetComponent<Renderer>().material.color = new Color(Random.value, Random.value, Random.value);
var radius = Random.Range(4, 7);
transform.localScale = new Vector3(radius,Random.Range(4,7),radius);
}
}
实现分数显示
整一个Canvas,添加一个text
把text的位置整到左上角
在player的脚本上添加text脚本,时刻更新text:
scoreText.text = score.ToString();
实现跌落重玩
添加碰撞检测代码,如果碰到地板或者跳了但是还在原来的小方块,重新开始:
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Ground") || other.gameObject == currentCube && jump)
{
// FindObjectOfType<GameManager>().EndGame();
SceneManager.LoadScene("MainScene");
}
else if (other.gameObject != currentCube)
{
score += 1;
inTheAir = false;
NewCube();
currentCube = other.gameObject;
}
}
player完整脚本
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Play : MonoBehaviour
{
public Rigidbody playerRigidbody;
public GameObject body;
public GameObject head;
public GameObject cubePrefab;
public Vector3 cubePlace;
private Queue<GameObject> cubes = new Queue<GameObject>();
public int speed = 10;
public int direction = 0;
public int maxCubeNumber = 10;
public Transform massCenter;
public GameObject currentCube;
public Text scoreText;
public int score = 1;
private bool jump = false;
private bool inTheAir = false;
private float start = 0;
// Start is called before the first frame update
void Start()
{
var cube = Instantiate(cubePrefab, cubePlace, Quaternion.identity);
cubes.Enqueue(currentCube);
cubes.Enqueue(cube);
GetComponent<Rigidbody>().centerOfMass = massCenter.localPosition;
}
private void FixedUpdate()
{
if (Input.touchCount > 0 && inTheAir == false)
{
jump = true;
if (body.transform.localScale.y > 0.5)
{
body.transform.localScale += new Vector3(1, -1, 1) * (0.45f * Time.deltaTime);
head.transform.localPosition += new Vector3(0, -1, 0) * (0.45f * Time.deltaTime);
}
var touch = Input.touches[0];
if (touch.phase == TouchPhase.Began)
{
start = Time.time;
}
else if (touch.phase == TouchPhase.Ended)
{
var time = Time.time - start;
if (time > 3)
{
time = 3;
}else if (time < 0.5f)
{
time = 0.5f;
}
if (direction == 0)
{
playerRigidbody.AddForce(new Vector3(1, 1, 0) * (time * speed), ForceMode.Impulse);
}
else
{
playerRigidbody.AddForce(new Vector3(0, 1, 1) * (time * speed), ForceMode.Impulse);
}
body.transform.localScale = new Vector3(1, 1.5f, 1);
head.transform.localPosition = new Vector3(0, 3.7f, 0);
inTheAir = true;
}
}
scoreText.text = score.ToString();
}
private void NewCube()
{
var random = new System.Random();
direction = random.Next(0, 2);
if (direction == 0)
{
var cube = Instantiate(cubePrefab, new Vector3(Random.Range(10, 13), 0, 0) + cubePlace,
Quaternion.identity);
cubePlace = cube.transform.position;
cubes.Enqueue(cube);
}
else
{
var cube = Instantiate(cubePrefab, new Vector3(0, 0, Random.Range(10, 13)) + cubePlace,
Quaternion.identity);
cubePlace = cube.transform.position;
cubes.Enqueue(cube);
}
if (cubes.Count > maxCubeNumber)
{
var cube = cubes.Dequeue();
Destroy(cube);
}
}
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Ground") || other.gameObject == currentCube && jump)
{
// FindObjectOfType<GameManager>().EndGame();
SceneManager.LoadScene("MainScene");
}
else if (other.gameObject != currentCube)
{
score += 1;
inTheAir = false;
NewCube();
currentCube = other.gameObject;
}
}
}
还有一些bug,欢迎发现,解决更好。