本文最后更新于 2024-07-13T17:49:49+08:00
角色控制,包括移动与多段跳
朝向
1 2 3 4
| float forward = Input.GetAxisRaw("Horizontal"); if (forward != 0) { transform.localScale = new Vector3(forward, 1, 1); }
|
移动
1 2 3 4 5 6 7 8 9 10 11
| public float speed; private Rigidbody2D body; void Start(){ body = GetComponent<Rigidbody2D>(); } void FixedUpDate(){ float horizontalMove = Input.GetAxis("Horizontal"); body.velocity = new Vector2(horizontalMove * speed * Time.fixedDeltaTime, body.velocity.y); }
|
跳跃
以跳跃作为核心玩法。
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
| public float jumpForce; public int finalJumpCount; public Transform footPoint; public LayerMask ground;
private int jumpCount; private bool jumpPressed = false, isJumped = false; private float time = -1f; void Update() { if (Input.GetButtonDown("Jump") && jumpCount > 0) { jumpPressed = true; time = Time.time + 0.05f; } if (time >= 0 && time < Time.time) { jumpPressed = false; time = -1f; } } void FixedUpdate(){ if (Physics2D.OverlapCircle(footPoint.position, 0.1f, ground)) { jumpCount = finalJumpCount; isJumped = false; } if (jumpPressed) { if (Physics2D.OverlapCircle(footPoint.position, 0.3f, ground)) { body.velocity = new Vector2(body.velocity.x, jumpForce); jumpCount--; jumpPressed = false; isJumped = true; } else if (isJumped) { body.velocity = new Vector2(body.velocity.x, jumpForce); jumpCount--; jumpPressed = false; } } }
|
玩家与收集物的交互,与敌人的交互。
收集品、简单的UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| using UnityEngine.UI;
public Text diamondCount; private int diamond = 0;
private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Cherry")) { Destroy(collision.gameObject); jumpCount++; isJumped = true; } if (collision.CompareTag("Diamond")) {; Destroy(collision.gameObject); diamond++; diamondCount.text = diamond.ToString(); } }
|
敌人
父类
首先创建所有敌人类的父类 Enemy,其中播放死亡动画和摧毁对象的方法对所有敌人来说都是通用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Enemy : MonoBehaviour { protected Animator animator; protected Rigidbody2D rb;
private int deathID; protected virtual void Start() { animator = GetComponent<Animator>(); rb = GetComponent<Rigidbody2D>(); deathID = Animator.StringToHash("Death"); } public void Death() { GetComponent<Collider2D>().enabled = false; rb.constraints = RigidbodyConstraints2D.FreezeAll; animator.SetTrigger(deathID); }
public void Disappear() { Destroy(gameObject); } }
|
Frog类
Frog 需要在一定范围内以跳跃的方式移动,这个坐标由他的两个子物体 leftPoint 和 rightPoint 提供。使用动画事件方便地使函数在正确的时机被调用。
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
| public class EnemyFrog : Enemy { public Transform leftPoint, rightPoint; public float speed; public float jumpForce; public LayerMask ground; public bool forwardLeft;
private Rigidbody2D body; private Collider2D collisionBox; private float leftX, rightX; private int jumpingID, fallingID; protected override void Start() { base.Start(); body = GetComponent<Rigidbody2D>(); collisionBox = GetComponent<Collider2D>(); leftX = leftPoint.position.x; rightX = rightPoint.position.x; Destroy(leftPoint.gameObject); Destroy(rightPoint.gameObject); jumpingID = Animator.StringToHash("Jumping"); fallingID = Animator.StringToHash("Falling"); } void Update() { if (animator.GetBool(jumpingID) && body.velocity.y <= 0) { animator.SetBool(jumpingID, false); animator.SetBool(fallingID, true); } if (animator.GetBool(fallingID) && collisionBox.IsTouchingLayers(ground)) { animator.SetBool(fallingID, false); body.velocity = new Vector2(0, 0); } } void movement() { if (forwardLeft) { if (transform.position.x <= leftX) { forwardLeft = false; transform.localScale = new Vector3(-1, 1, 1); } body.velocity = new Vector2(-transform.localScale.x * speed, jumpForce); animator.SetBool(jumpingID, true); } else { if (transform.position.x >= rightX) { forwardLeft = true; transform.localScale = new Vector3(1, 1, 1); } body.velocity = new Vector2(-transform.localScale.x * speed, jumpForce); animator.SetBool(jumpingID, true); } } }
|
PlayerController类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag("Enemy")) { if (animator.GetBool(fallingID) && transform.position.y - collision.transform.position.y > 0.35f) {
Enemy enemy = collision.gameObject.GetComponent<Enemy>(); enemy.Death(); body.velocity = new Vector2(body.velocity.x, jumpForce); jumpCount = finalJumpCount; isJumped = true; } else { isHurt = true; if (transform.position.x <= collision.transform.position.x) { body.velocity = new Vector2(-10f, body.velocity.y + jumpForce * 0.7f); } else { body.velocity = new Vector2(10f, body.velocity.y + jumpForce * 0.7f); } } } }
|
对话框、运动透视、菜单中调节音量。
对话框提示
对话框的渐入渐出和移动由录制动画实现,通用的 Dialog 类挂载在一个 Trigger 上,负责动画的切换。
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
| public class Dialog : MonoBehaviour { public GameObject dialog;
private Animator animator; private int enterID,exitID; private void Start() { animator = dialog.GetComponent<Animator>(); enterID = Animator.StringToHash("Enter"); exitID = Animator.StringToHash("Exit"); } private void OnTriggerStay2D(Collider2D collision) { if (collision.CompareTag("Player") && !dialog.activeSelf) { dialog.SetActive(true); animator.SetBool(enterID, true); animator.SetBool(exitID, false); } } private void OnTriggerExit2D(Collider2D collision) { if (collision.CompareTag("Player")) { animator.SetBool(enterID, false); animator.SetBool(exitID, true); Invoke("setActiceFalse", 0.1f); } } private void setActiceFalse() { dialog.SetActive(false); } }
|
运动透视
将这个脚本挂载在某一层背景上。
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
| public class Parallax : MonoBehaviour { public new Transform camera; public float moveRate; public bool lockY;
private float bgStartX, bgStartY; private float cmStartX, cmStartY; void Start(){ bgStartX = transform.position.x; bgStartY = transform.position.y;
cmStartX = camera.position.x; cmStartY = camera.position.y; } void Update(){ float difX = camera.position.x - cmStartX; float difY = camera.position.y - cmStartY;
if (lockY) { transform.position = new Vector2(bgStartX + difX * moveRate, transform.position.y); } else { transform.position = new Vector2(bgStartX + difX, bgStartY + difY) * moveRate; } } }
|
音量调节
将所有 Source 输出到一个 Mixer 内,若想更新 Mixer 的属性(比如音量),需先将音频组检视面板中的属性暴露出来再 Set。再将该函数交由一 SliderUI 调用,动态地传入值即可。
1 2 3
| public void SetAudioVolume(float volume) { audioMixer.SetFloat("MainVolume", volume); }
|
相关链接
M_Studio
Input
Rigidbody2D
Physics2D
RigidbodyConstraints2D
timeScale