地图
Creat
Creat -> 2D object -> Tilemap -> Rectangle
Layer
Add component
-
Tilemap Collider 2D
-
Rigidbody 2D
-
Composite Collider 2D
画板Palette
Edit
Find the Asserts-> Terrian, and adjust the size (100->16, Pixels Per Unit), use Sprite Editor to edit it.
Add Tile Palette
windows-> 2D -> Tile Palette -> Create New Palette
Import: Drag the Terrian asserts to the Palette
背景
A new tilemap the Copyies TileMap and we’ll rename it as ‘background’.
In addition, we won’t attach any component to it.
Layer
The layer of it should be the minium.
Palette
Just as the TileMap, we should adjust the size of assert.
PPU 越小,图像越大
Import: Drag the Terrian asserts to the Palette
玩家Player
Creat
Creat -> 2D object -> Sprites -> Rectangle…
Add Component
- Rigidbody 2D
-
Box Collider 2D or other … Capsule …
Use this button to edit the box.
Add Script
Creat -> C# Script -> name
Add a PlayerMovement component
PlayerMovement.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Unity.VisualScripting;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PlayerMovement : MonoBehaviour
{
private Animator anime;
private Rigidbody2D rb;
[Header("Player info")]
[SerializeField] private float movespeed;
[SerializeField] private float jumpforce;
[Header("Collision info")]
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float groundCheckRadius;
private float xInput;
private bool isGrounded = true;
//Facing direction
private int facingdir = 1;
private bool isFacingRight = true;
[SerializeField] private AudioSource jumpSound;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
anime = GetComponentInChildren<Animator>();
}
// Update is called once per frame
void Update()
{
CheckGroundStatus();
CheckInput();
move();
FlipController();
AnimationController();
}
private void CheckInput()
{
xInput = Input.GetAxis("Horizontal");
if (Input.GetKeyDown(KeyCode.R))
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Jump");
jump();
}
}
private void move()
{
rb.linearVelocity = new Vector2(xInput * movespeed, rb.linearVelocity.y);
if(rb.transform.parent)
{
transform.localPosition += new Vector3(xInput * 0.5f * Time.deltaTime, 0, 0);
}
}
private void jump()
{
if(isGrounded)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpforce);
jumpSound.Play();
}
else
{
Debug.Log("Not grounded");
}
}
private void AnimationController()
{
bool isMoving = Mathf.Abs(rb.linearVelocity.x) > 0.1f && xInput != 0;
bool isFalling = rb.linearVelocity.y < -0.1f;
bool isJumping = rb.linearVelocity.y > 0.1f;
anime.SetBool("IsMoving", isMoving);
anime.SetBool("IsFalling", isFalling);
anime.SetBool("IsJumping", isJumping);
}
private void FlipController()
{
if( rb.linearVelocity.x *facingdir < 0)
Flip();
}
private void Flip()
{
transform.Rotate(0, 180, 0);
facingdir *= -1;
isFacingRight = !isFacingRight;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = false;
}
}
private void CheckGroundStatus()
{
isGrounded = Physics2D.OverlapCircle(transform.position, groundCheckRadius, groundLayer);
}
}
Then we should add it to Player as a component
==Animation==
1.Creat
Creat an Empty object under the Player and name it as Animator.
Then we should delete the Player Sprites Renderer and add it to Animator
And you can edit the Sprite from all your asserts
2.Window
window->Animation -> Animation
window->Animation -> Animator
3.Creat Controller /Anim
creat -> Animation Controller
Import: Drag it to Animator
Creat anim (click Animator)
if you have already a anim, creat new clips
then we can add animation frames to .anim(Shift+click, drag them to the linebox ), and adjust the Samples to balance the speed
4.Transition
As you can see, we have different animation, but how can we use them?
==Set Parameters==:
Right-clik the object module and make transition to other modules:
Inspect the transition, add conditions
cancel the duration:
==Edit Script==:
private Animator anime;//
void Start()
{
//...
anime = GetComponentInChildren<Animator>();//
}
void Update()
{
//...
isMoving= rb.velocity.x!= 0;
anime.SetBool("IsMoving", isMoving);//assign isMoving to IsMoving
}
Flip
private int facingdir = 1;
private bool isFacingRight = true;
private void FlipController()
{
if( rb.velocity.x *facingdir < 0)
Flip();
}
private void Flip()
{
transform.Rotate(0, 180, 0);
facingdir *= -1;
isFacingRight = !isFacingRight;
}
==CollisionChecks /StickPlatform==
[Header("Collision info")]
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float groundCheckRadius;
private bool isGrounded;
private void CollisionChecks()
{
isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundCheckRadius, groundLayer);
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(transform.position,
new Vector3(transform.position.x, transform.position.y - groundCheckRadius));
}
Adjust the Radius until the line touches the ground
Set the layer of the TileMap as ground and edit the parameters of Player
为Tilemap 设置碰撞箱。添加Composite Collider 2D组件,设置Tag = Ground
StickyPlatform.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StickyPlatform : MonoBehaviour
{
void Update()
{
CheckGroundStatus();
//...
}
//触发碰撞
private void OnTriggerEnter2D(Collider2D collison)
{
//将碰撞体设置为自己的子物体
if(collison.gameObject.tag == "Player")
{
collison.gameObject.transform.SetParent(transform);
}
}
//碰撞结束
private void OnTriggerExit2D(Collider2D collison)
{
//将碰撞体的父级置空
if (collison.gameObject.tag == "Player")
{
collison.gameObject.transform.SetParent(null);
}
}
}
private void CheckGroundStatus()//额外的检测,在update中不断检测状态,防止出现状态错误
{
isGrounded = Physics2D.OverlapCircle(transform.position, groundCheckRadius, groundLayer);
}
墙附着问题Sticky Walls
Creat -> 2D -> Physics 2D material ->Friction ->0
Drag the material to Player Rigidbody material
物品收集器ItemCollector & UI Text
Creat-> UI -> Legacy -> Text
Install a Font from web
Add Script to Player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;//**
public class ItemCollector : MonoBehaviour
{
private int apples = 0;
[SerializeField] private Text appleText;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Apple"))
{
apples++;
appleText.text = "Apples: "+apples;
Destroy(collision.gameObject);
}
}
}
Edit the Item Trigger and Tag (Apple)
Adjust the text window position
Death & Restart
Creat a Spike Trap just like Apple and set it Tag as Trap
PlayerDeath.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;//
using UnityEngine.SceneManagement;//
public class PlayerLife : MonoBehaviour
{
private Animator anime;
private Rigidbody2D rb;
[SerializeField] private Text deathText;
void Start()
{
rb = GetComponent<Rigidbody2D>();
anime = GetComponentInChildren<Animator>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Trap"))
{
Death();
}
}
private void Death()
{
rb.bodyType = RigidbodyType2D.Static;
deathText.text = "Game Over\nPress R to restart";
anime.SetTrigger("Death");//
}
}
ReStart.cs 绑定到Animator通过Animation触发
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ReStart : MonoBehaviour
{
private void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
Apply the Script to Player and Animator
Death Animation
- Record the key frame by cancel the Sprite Renderer
- Add event Restart and bind it with Function Restart()
平台Platform
创建一个empty对象命名为Platforms,表示路径点和平台的集合体
point设置参考 循环移动
设置平台的触发器和碰撞箱
触发器设置在上面较小的区域,使得角色不会被其他部分附着(Setparent)
WayPoints.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WayPointSaw : MonoBehaviour
{
[SerializeField] private GameObject[] wayPoints;
private int currentWaypointIndex = 0;
[SerializeField] private float speed = 5f;
void Update()
{
if(Vector2.Distance(wayPoints[currentWaypointIndex].transform.position, transform.position) < 0.1f)
{
currentWaypointIndex = (currentWaypointIndex + 1) % wayPoints.Length;
}
transform.position = Vector2.MoveTowards(transform.position, wayPoints[currentWaypointIndex].transform.position, speed * Time.deltaTime);
}
}
StickyPlatform.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StickyPlatform : MonoBehaviour
{
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
collision.gameObject.transform.SetParent(transform);
}
}
private void OnCollisionExit2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
collision.gameObject.transform.SetParent(null);
}
}
}
BGM/音效
BGM
下载音乐资源:
Casual game sounds
创建空物体,添加组件Audio Source,绑定音效/BGM
音效
在角色身上添加组件Audio Source,绑定音效/BGM
[SerializeField] private AudioSource jumpSound; //在监视器内绑定Audio Source
private void jump()
{
if(isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpforce);
jumpSound.Play();
}
}
其余音效同理
场景跳转
将需要的场景添加到BuildSettins (File)调整顺序
跳转脚本,可以通过Button触发或者通过碰撞事件:
按钮: 触发事件需要绑定为按钮对象,同时设定触发函数为Startmenu里面的StartGame函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class StartMenu : MonoBehaviour
{
public void StartGame()
{
// Start the game
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
碰撞事件:
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player" && !levelComplete)
{
audiences.Play();
levelComplete = true;
Invoke("NextLevel", 1f); //1s delay
}
}
private void NextLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
物体循环移动
普通左右移动:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows;
public class PlatformMovement : MonoBehaviour
{
// Start is called before the first frame update
private Animator anime;
private Rigidbody2D rb;
[SerializeField] private float speed=2f;
private int direction = 1 ;
void Start()
{
rb=GetComponent<Rigidbody2D>();
anime=GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
if (transform.position.x <= 4f )
direction = 1;
else if (transform.position.x >= 10f)
direction = -1;
move();
}
private void move()
{
rb.velocity=new Vector2(direction*speed,0);
}
}
优点是简单好写,缺点是可移植性差,需要反复改坐标
设置路径点:
在监视器内设置路径点(空物体),可设置多个路径点,甚至可以朝玩家飞过来
[SerializeField] private GameObject[] wayPoints;
private int currentWaypointIndex = 0;
[SerializeField] private float speed = 5f;
void Update()
{
if(Vector2.Distance(wayPoints[currentWaypointIndex].transform.position, transform.position) < 0.1f)
{
currentWaypointIndex = (currentWaypointIndex + 1) % wayPoints.Length;
}
transform.position = Vector2.MoveTowards(transform.position, wayPoints[currentWaypointIndex].transform.position, speed * Time.deltaTime);
}
用Ctrl D 可以直接复制一份路径点,拖动即可。把路径点的y值设得比物体低一点,可以减少抖动问题。
值得注意的是,这样写只是在不断改变 transform.position ,实际上物体的速度是0
改变速度实现移动
private void move()
{
// 获取当前目标路径点的位置
Vector3 targetPosition = wayPoints[currentWaypointIndex].transform.position;
if (Vector2.Distance(wayPoints[currentWaypointIndex].transform.position, transform.position) < 0.2f)
{
currentWaypointIndex = (currentWaypointIndex + 1) % wayPoints.Length;
}
Vector2 direction = (targetPosition - transform.position).normalized;
// 移动
rb.velocity = new Vector2(direction.x * moveSpeed, rb.velocity.y);
}
利用静态对象继承属性
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AllControl : MonoBehaviour
{
public class GameManager
{
private static GameManager instance;
public static GameManager Instance
{
get
{
if (instance == null)
{
instance = new GameManager();
}
return instance;
}
}
public int score = 0;
}
}
用法:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using static AllControl;// 声明
public class ItemCollector : MonoBehaviour
{
private int apples = GameManager.Instance.score;//GameManager为AllControl里面的静态实例,可以通过这个方法来实现全局,以及跨文件调用
[SerializeField] private Text appleText;
[SerializeField] private AudioSource audioSource;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Apple"))
{
Apple();
Destroy(collision.gameObject);
}
}
private void Apple()
{
apples++;
appleText.text = "Apples: " + apples;
audioSource.Play();
GameManager.Instance.score = apples;//
}
}
Comments