当前位置: 首页 > article >正文

四、综合案例(Unity2D)

一、2D渲染

1、2D相机基本设置

上面是透视,下面是正交

2、图片资源

在Unity中,常规图片导入之后,一般不在Unity中直接使用,而是转为精灵图Sprite

将图片更改为即可使用Unity内置的图片切割功能

无论精灵图片是单个的还是多个的,都可以选择中心点

3、相关组件

 

在排序图层中,越靠下的,越先渲染

也就是说,靠近摄像机的因该是最下方的图层

二、Unity输入系统

1、按键检测

关于键盘的

if (Input.GetKeyDown(KeyCode.A))
{
    Debug.Log("A按下了!");
}
if (Input.GetKey(KeyCode.A))
{
	Debug.Log("A持续按住!");
}
if (Input.GetKeyUp(KeyCode.A))
{
	Debug.Log("A弹起了!");
}

 

2、鼠标检测

和键盘区别不大,只是调用的API不同

注:0是左键;1是右键;2是鼠标中键

if (Input.GetMouseButtonDown(0))
{
	Debug.Log("鼠标左键按下");
}
if (Input.GetMouseButton(0))
{
	Debug.Log("鼠标左键按住");
}
if (Input.GetMouseButtonUp(0))
{
	Debug.Log("鼠标左键抬起");
}

 

3、InputManager

在Unity的设置中找到输入部分,即可看到默认绑定的按键

一个简单的移动代码

float inputX = Input.GetAxis("Horizontal");
float inputY = Input.GetAxis("Vertical");
float speed = 5;
transform.Translate(Time.deltaTime * speed * new Vector3(inputX, inputY, 0));

注:这里的速度乘以时间,放在前面,避免一些不必要的消耗

三、Unity2D动画概览

1、Animator组件

先在窗口上添加一个动画组件

一定是下面这个。上面这个是比较老的技术了

2、AnimatorController:动画控制器

3、AnimationClip:动画片段 

四、动画录制 

在Unity内制作动画

在录制时,必须开启录制按钮,可以选择数据,也可以修改数据,录制时会自动记录下来

也可以添加颜色

 五、动画切换

 

通过参数进行切换,小于或者大于

 2D人物的简单移动代码

public Animator animator;
public float movespeed = 0.00001f;
public SpriteRenderer spriteRenderer;
private void Start()
{
	spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
}

// Update is called once per frame
void Update()
{
	animator.GetComponent<Animator>();
	float inputX = Input.GetAxis("Horizontal");
    float inputY = Input.GetAxis("Vertical");
	//修改动画
    bool isMove = inputX != 0 || inputY!=0;
	animator.SetBool("Move", isMove);
    //转向
    if (inputX > 0) spriteRenderer.flipX = false;
    else if (inputX < 0) spriteRenderer.flipX = true;

	//位移
	Vector3 pos =
            new Vector3(inputX * Time.deltaTime * movespeed,
                        inputY * Time.deltaTime * movespeed,
                        0);
    transform.Translate(pos, Space.Self);
}

六、动画事件

在动画的某一帧触发某个函数;

原理是:在当前动画物体上的脚本里面 是否有一个事件;必须是公开的

在此处添加动画事件;

选择动画事件代码

public void Colore()
{
    transform.localScale=new Vector3(3, 3, 3);
    Debug.Log("1");
}

 

执行到这个事件时,会执行这个函数 

七、2D物理系统

每个人都需要有碰撞器,只要有一个有刚体

事件触发函数

private void OnCollisionEnter2D(Collision2D collision)
{
	
}
private void OnCollisionStay2D(Collision2D collision)
{
	
}
private void OnCollisionExit2D(Collision2D collision)
{
	
}

碰撞与触发不能共存

触发函数代码

private void OnTriggerEnter2D(Collider2D collision)
{
	
}
private void OnTriggerStay2D(Collider2D collision)
{
	
}
private void OnTriggerExit2D(Collider2D collision)
{
	
}

 八、主角控制(综合案例)

在玩家身上挂载一个触发器,不需要碰撞

挂载脚本

public class Game : MonoBehaviour
{
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
    bool Big=false;
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
	}

	// Update is called once per frame
	void Update()
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
        float inputY = Input.GetAxis("Vertical");
		//修改动画
        bool isMove = inputX != 0 || inputY!=0;
		animator.SetBool("Move", isMove);
        //转向
        if (inputX > 0) spriteRenderer.flipX = false;
        else if (inputX < 0) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
                new Vector3(inputX * Time.deltaTime * movespeed,
                            inputY * Time.deltaTime * movespeed,
                            0);
        transform.Translate(pos, Space.Self);
    }
   
}

由于人物比较小,调节一下摄像机距离

九、发射子弹

子弹一般都是预制体;

需要一个公开获取预制体;一个生成子弹的点位;一个if循环判断按键和实例化

子弹飞行是自己的逻辑;挂载在预制体上面;

private void Shoot()
{
	if (Input.GetKeyDown(KeyCode.J))
	{
		//实例化
		GameObject star=GameObject.Instantiate(starPrefab);
		star.transform.position = starShootPoint.position;
	}
}

创建一个动画,模拟子弹射出

旋转360° 然后旋转720°

将动画勾选为循环

在预制体子弹上面挂在一个动画状态机,放入旋转动画

挂载子弹的代码


    public float moveSpeed;
    private Vector3 moveDir;
    public float destroyTime=3;
    private float destroyTimer;//计时器
    public void Init(Vector3 dir)
    {
        moveDir = dir;
        destroyTimer = destroyTime;
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);
        if (destroyTimer <= 0)
        {
            Destroy(gameObject);
        }else
        {
            destroyTimer-=Time.deltaTime;
        }
    }

由于子弹自身有旋转,所以坐标会一直更改,需要传入一个方向初始化

在玩家代码中调用

//实例化
GameObject star=GameObject.Instantiate(starPrefab);
star.transform.position = starShootPoint.position;
bool isRightDir = spriteRenderer.flipX == false;
Vector3 moveDir = isRightDir ? transform.right : -transform.right;
Debug.Log(moveDir);
star.GetComponent<Star>().Init(moveDir);

先实例化这个预制体;把生成点赋值给预制体的当前坐标

通过bool判断是左还是右

然后获取预制体代码中的初始化方法,并传入方向

然后写一个计时器,让子弹销毁

public class Game : MonoBehaviour
{
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
	public GameObject starPrefab;
	public Transform starShootPoint;

	//CD
	private float shootCD = 1;
	private float shootCDTimer;
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
	}

	// Update is called once per frame
	void Update()
    {
		Move(); Shoot();

	}
   private void Move()
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
		float inputY = Input.GetAxis("Vertical");
		//修改动画
		bool isMove = inputX != 0 || inputY != 0;
		animator.SetBool("Move", isMove);
		//转向
		if (inputX > 0) spriteRenderer.flipX = false;
		else if (inputX < 0) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
				new Vector3(inputX * Time.deltaTime * movespeed,
							inputY * Time.deltaTime * movespeed,
							0);
		transform.Translate(pos, Space.Self);
	}
	private void Shoot()
	{
		if (shootCDTimer > 0)
		{
			shootCDTimer-= Time.deltaTime;
			return;
		}
		if (Input.GetKeyDown(KeyCode.J))
		{
			shootCDTimer = shootCD;
			//实例化
			GameObject star=GameObject.Instantiate(starPrefab);
			star.transform.position = starShootPoint.position;
			bool isRightDir = spriteRenderer.flipX == false;
			Vector3 moveDir = isRightDir ? transform.right : -transform.right;
			Debug.Log(moveDir);
			star.GetComponent<Star>().Init(moveDir);
		}
	}
}

十、怪物生成

1、配置一些坐标生成怪物

创建一个空物体,挂载怪物的代码

对怪物的基础配置进行设置:

这里有一个方法;使用class类,然后把这个类作为一个数组,公开这个数组为public即可

/// <summary>
/// 怪物生成点
/// </summary>
[Serializable]
public class MonsterSpawnPoint
{
    public Transform spawnPoint;//生成点
    public GameObject monsterPrefab;//怪物预制体
    public float spawnInterval;//怪物生成间隔
    //这里的计时器不想序列化,但是需要获取,所以改为属性
    public float SpawnTimer { get; set; }
}
public MonsterSpawnPoint[] monsterSpawnPoints;

 

小技巧:把动画片段全部选中拖拽进入层级面板中,可以快速地生成动画和动画状态

2、按照一个频率生成怪物

现在面板上设置了预制体和生成间隔

我们做一个计时器,计时器命名为Timer

用一个for循环遍历这5个数组

声明一个局部变量类,让每一次循环都赋值给这个局部变量类,也就是每一次循环Point都获取了数组中的参数;

使用计时器,先让他小于零;

做一个判断如果小于零,那么就把生成间隔赋值给计时器;

并且实例化类中的预制体,把类中的生成点赋值给怪物的当前位置;

private void Update()
{
    for (int i=0;i< monsterSpawnPoints.Length;i++)
    {
		MonsterSpawnPoint point= monsterSpawnPoints[i];
        point.SpawnTimer-=Time.deltaTime;//这里的计时器一开始就会小于零
        if (point.SpawnTimer <= 0)//一定会小于零
        {
            point.SpawnTimer = point.spawnInterval;//就把生成间隔赋值给计时器
            GameObject monster = GameObject.Instantiate(point.monsterPrefab);
            monster.transform.position = point.spawnPoint.position;
        }
	}
}

现在对代码进行一下优化,

public class MonsterManager : MonoBehaviour
{
    /// <summary>
    /// 怪物生成点
    /// </summary>
    [Serializable]
    public class MonsterSpawnPoint
    {
        public Transform spawnPoint;//生成点
        public GameObject monsterPrefab;//怪物预制体
        public float spawnInterval;//怪物生成间隔
        //这里的计时器不想序列化,但是需要获取,所以改为属性
        public float SpawnTimer { get; set; }

       public void Update(float deltaTime)
        {
			SpawnTimer -= Time.deltaTime;//这里的计时器一开始就会小于零
			if (SpawnTimer <= 0)//一定会小于零
			{
				SpawnTimer = spawnInterval;//就把生成间隔赋值给计时器
				GameObject monster = GameObject.Instantiate(monsterPrefab);
				monster.transform.position = spawnPoint.position;
			}
		}
    }
    public MonsterSpawnPoint[] monsterSpawnPoints;
	private void Update()
	{
        for (int i=0;i< monsterSpawnPoints.Length;i++)
        {
			monsterSpawnPoints[i].Update(Time.deltaTime);
           
		}
	}
}

因为update中调用其他类中的变量,不如直接调用update封装起来

所以在Update中定义一个参数(时间),因为调用这个update传入的参数是Time.deltatime

把生成的逻辑封装起来在Update中

在主update中,每一个for循环之后,数组的第i次循环调用这个UPdate,并传入每时每秒。

十一、怪物移动逻辑

一直去追玩家,如果解除了玩家则玩家失败;

首先把玩家设置成单例;在怪物类中先设置一个判断;判断玩家是否为空,也就是死亡状态

if (Game.Instance == null) return;

计算玩家到敌人的距离,方法是使用向量,用敌人的当前坐标 减去 自己的当前坐标

Vector3 moveDir=Game.Instance.transform.position-transform.position;

 然后进行位移

transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);

 此时敌人背面朝着敌人移动,所以使用Bool判断玩家在左或者右

 如果玩家的坐标小于怪物的坐标,说明是在左侧,那么更改怪物的缩放值X

bool isRight=transform.position.x>Game.Instance.transform.position.x;
if (isRight) transform.localScale = new Vector3(-1, 1, 1);
else transform.localScale = new Vector3(1, 1, 1);

 

十二、子弹消灭怪物

1、怪物被攻击,减少血量

首先,把死亡的动画片段拖进两个敌人的动画控制器中

我选择使用触发 作为死亡的条件

//接触子弹
if (collision.gameObject.CompareTag("Bullet"))
{
	
	Destroy(collision.gameObject);//取消子弹穿透
	hp -= 1;
	if (hp == 0)
	{
		animator.SetTrigger("Death");
	}
}

如果血量为零,则播放死亡动画 

2、血量为0,播放死亡动画,销毁自己

#region 动画事件
	private void OnDeathAnimationEnd()
	{
		Destroy(gameObject);
	}
#endregion
	

 在死亡动画的最后一帧执行销毁

十三、鼠标指向为攻击方向 

使用屏幕坐标 到 世界坐标 

public new Camera camera;
		Vector3 mouseWorldPosition=camera.ScreenToWorldPoint(Input.mousePosition);

获取到的屏幕坐标,因为两个方法都需要,所以作为参数传进来 

十四、玩家生命值以及状态 

不适用UI;

把血条作为摄像机的子物体,防止血条跟随摄像机改变位置;

不让摄像机在玩家的子物体下,通过代码跟随

使用LateUpdate,让摄像机慢一帧追逐玩家

public Transform target;
private void LateUpdate()
{
	transform.position = target.position;
}

 此时,出现问题。

因为2d的,摄像机到玩家的距离是-10,运行后摄像机与玩家重合会看不见

修改代码,固定Z轴坐标

Vector3 pos=target.position;
pos.z = -10;
transform.position = pos;

因为血条是几张图片切换实现的

所以,在玩家身上公开一个变量HP,把Hp设置为字段,get无所谓,set设置切换 

[SerializeField]private int hp;
public int Hp 
{
	get => hp;
	set
	{ 
		hp = value;
	} 
}

然后引用血量图,做成一个数组 

public SpriteRenderer hpSpriteRenderer;
public Sprite[] spriteRenderers;

然后在字段中,把精灵图第几个赋值给血条上 

public int Hp 
{
	get => hp;
	set
	{ 
		hp = value;
		hpSpriteRenderer.sprite = hpSprites[hp];
	} 
}

初始化中,把数组长度作为血量值传进来

private void Awake()
{
	Instance = this;
	Hp = hpSprites.Length-1;
}

十五、怪物干掉玩家

怪物碰到玩家,掉血,玩家掉血为0则死亡

在此处遇到问题:怪物与玩家无法碰撞,碰撞代码逻辑未执行;

原因是刚体的数值未设置正确。

public class CameraController : MonoBehaviour
{
	public Transform target;
	private Game game1;
	
	private void Awake()
	{
		game1 = FindObjectOfType<Game>();
		if (game1 == null)
		{
			Debug.Log("没找到");
		}

	}
	private void LateUpdate()
	{
		int xue = game1.Hp;
		Debug.Log(xue);
		if (xue == 0)
		{
			return;
		}
		Vector3 pos=target.position;
		pos.z = -10;
		transform.position = pos;
	}
}

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//using UnityEngine.Windows;

public class Game : MonoBehaviour
{
	public static Game Instance;
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
	public GameObject starPrefab;
	public Transform starShootPoint;

	public new Camera camera;

	[SerializeField]private int hp;
	public SpriteRenderer hpSpriteRenderer;
	public Sprite[] hpSprites;

	//CD
	private float shootCD = 0.1f;
	private float shootCDTimer;

	public int Hp 
	{
		get => hp;
		set
		{ 
			hp = value;
			hpSpriteRenderer.sprite = hpSprites[hp];
			if (hp == 0)
			{
				Dead();
			}
		} 
	}

	
	private void Awake()
	{
		Instance = this;
		Hp = hpSprites.Length-1;
	}
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
		
	}

	// Update is called once per frame
	void Update()
    {
		Debug.Log(Hp);
		Vector3 mouseWorldPosition=camera.ScreenToWorldPoint(Input.mousePosition);
		//Debug.Log(mouseWorldPosition);
		Move(mouseWorldPosition); Shoot(mouseWorldPosition);

	}
   private void Move(Vector3 mouseWorldPosition)
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
		float inputY = Input.GetAxis("Vertical");
		//修改动画
		bool isMove = inputX != 0 || inputY != 0;
		animator.SetBool("Move", isMove);
		//转向
		if (mouseWorldPosition.x > transform.position.x) spriteRenderer.flipX = false;
		else if (mouseWorldPosition.x < transform.position.x) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
				new Vector3(inputX * Time.deltaTime * movespeed,
							inputY * Time.deltaTime * movespeed,
							0);
		transform.Translate(pos, Space.Self);
	}
	private void Shoot(Vector3 mouseWorldPosition)
	{
		if (shootCDTimer > 0)
		{
			shootCDTimer-= Time.deltaTime;
			return;
		}
		if (Input.GetMouseButton(0))
		{
			shootCDTimer = shootCD;
			//实例化
			GameObject star=GameObject.Instantiate(starPrefab);
			star.transform.position = starShootPoint.position;
			Vector3 moveDir = (mouseWorldPosition - transform.position).normalized;
			star.GetComponent<Star>().Init(moveDir);
		}
	}

	internal void Hurt()
	{
		if (Hp <= 0) return;
		Hp -= 1;
	}
	private void Dead()
	{
		animator.SetTrigger("Dead");
	}
	private void DestroyPlayer()
	{
		Destroy(gameObject);
	}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
	public float moveSpeed;
	public int hp;
	public Animator animator;
	private void Update()
	{
		if (Game.Instance == null) return;
		Vector3 moveDir=Game.Instance.transform.position-transform.position;
		transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);
		bool isRight=transform.position.x>Game.Instance.transform.position.x;
		if (isRight) transform.localScale = new Vector3(-1, 1, 1);
		else transform.localScale = new Vector3(1, 1, 1);
	}
	private void OnTriggerEnter2D(Collider2D collision)
	{
		Debug.Log("碰撞对象标签:" + collision.gameObject.tag);
		if (hp == 0) return;
		//接触子弹
		if (collision.gameObject.CompareTag("Bullet"))
		{
			Debug.Log("子弹接触怪物了");
			Destroy(collision.gameObject);//取消子弹穿透
			hp -= 1;
			if (hp == 0)
			{
				animator.SetTrigger("Death");
			}
		}
		else
		{
			//接触玩家
			if (collision.gameObject.CompareTag("Player"))
			{
				Debug.Log("11");
				collision.gameObject.GetComponent<Game>().Hurt();
				Debug.Log("怪物接触玩家了");
			}
		}
		
	}
	
	#region 动画事件
	private void OnDeathAnimationEnd()
	{
		Destroy(gameObject);
	}
#endregion
	
}


http://www.kler.cn/a/559504.html

相关文章:

  • React入门案例-Hello React案例
  • 图论 之 弗洛伊德算法求解全源最短路径
  • QT闲记-工具栏
  • ICRA2024:CoLRIO,用于机器人群体的激光雷达测距-惯性集中状态估计
  • Linux | 进程控制(进程终止与进程等待)
  • Springboot中分析SQL性能的两种方式
  • TIKTOK矩阵系统的软件服务
  • C#上位机--循环语句
  • Unity VRoid+Blender+Unity 3D人物模型导入使用
  • DeepSeek掘金——基于DeepSeek-R1构建文档问答机器人
  • Linux 实用指令
  • ubuntu新系统使用指南
  • Ollama Open WebUI
  • 【Transformer架构】
  • AI知识架构之数据采集
  • React AJAX:深入理解与高效实践
  • matlab-17dof列车横向动力学模型
  • Unity Shader Graph 2D - 一个简单的魔法阵激活效果
  • 改BUG:Mock测试服务层的时候,应注入服务类的实现,而不是接口。
  • 生活教练项目_Trae