[Unity Demo]从零开始制作空洞骑士Hollow Knight第十二集:制作完整地图和地图细节设置以及制作相机系统的跟随玩家和视角锁定功能

news2024/9/29 9:51:55

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、制作完整的地图和地图细节设置
    • 1.制作地图前的设置
    • 2.制作地图前期该做的事
    • 3.制作地图之堆叠素材
    • 4.制作地图后期该做的事
    • 5.制作地图之修复意想不到的Bug
  • 二、制作摄像机系统
    • 1.制作相机跟随玩家系统
    • 2.制作相机视角锁定系统
  • 总结


前言

Hello大家好久不见,隔了几天没发文章并不是这几天放假了而是因为这期的工程量有点大,找素材和堆叠素材到场景中花费了很多时间,然后再制作一个完整的摄像机系统,我放弃了安装插件Cinemachine和Pro Camera2D之类的相机插件,而是自建了一个摄像机系统,所以花费的时间有点多,还有就是我做上头了,本来制作地图就够出一期了,但我做完后觉得不爽还是等做了一个摄像机系统才想起来要写文章。

OK话不多说,这期我们的主题是制作一张的完整地图和地图细节设置以及制作相机跟随玩家系统


一、制作完整的地图和地图细节设置

  • 1.制作地图前的设置

制作地图之前,我们可以给我们之前的场景命名叫Test_01,然后创建一个新的创建叫Tutorial_01,先给buildSetting上添加上去吧。

这些东西在其它场景也得用得到,所以我们可以先把他们当预制体放好,在新建的场景打开:‘

在新场景Tutorial_01我们先设置好相机吧,我们可以添加一个tk2dCamera,然后像我这样设置:

X和Y坐标无所谓,主要是Z轴为-38.1,Layer设置为UI,后面我们会做一个渲染UI的摄像机hudCamra,所以渲染层级要取消UI的层级

  • 2.制作地图前期该做的事

看到这里你是不是很想赶紧把素材箱里的图片素材全部堆进去呢,但别急,我们制作地图的时候可以想一想一个地图大概是什么样子的,想一想我们进入空洞骑士游戏的第一个场景,我们可以先绘制一个大概的模样,那到底要怎么做呢,这里我们可以用tk2dTilemap来实现!

右键创建游戏对象->tk2d->tilemap,你就可以看到这两个东西

点开tilemap,找到settings选项框,如果这里为空你就直接创建一个,

我们再来创建一个tk2dSprite,很简单,一张黑幕的图片:

在这里选择我们刚刚创建的TilemapSpriteCollection

除此之外,我们还要设置好图片的大小啊,地图的大小啊等等:

设置好Terrain的Layer层级设置和Physics Material

看看你的Tile Proteriles是不是和我一样

创建好后点开Paint选项,然后就可以看到我们创建好的Tiles了

同时你也注意到我的Scene场面左上角有类似于unity自带的Tile Palette画板系统,我们选择好palette后就可以点击最左边的画笔开画了:

下面这个就是我画的:

然后就是给每一个Chunk添加好碰撞箱Edge Collider 2D:

添加完成后如下:

 

  • 3.制作地图之堆叠素材

        好了。你已经学会了怎么绘制一张图前期该做的事了,首先创建一个原点位置的游戏对象_Scenery,

        下面试着画出这样的地图吧:

没办法,这个就是自己调整每一个素材的Transform,这个得根据你有的素材来决定的,但我还是会挑几个可交互的物体来讲解:

首先是萤火虫。添加好tk2dSprite和tk2dSpriteCollection,以及子对象的tk2dSprite的灯光

 同时它还需要一个playmakerFSM,只有一个行为,就是Idle:

 可破坏的雕塑breakable Pole:

两个particle_rocks的粒子系统如下,这里我们暂时用不到它们

然后是一个被破坏后的头部Tute Pole 4 Top,它需要一个RB2D和Col2D,同时还要注意的是它的层级Corpse,只能和地面的Layer发生碰撞 

 添加好脚本BreakablePole.cs:

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

public class BreakablePole : MonoBehaviour,IHitResponder
{
    [SerializeField] private SpriteRenderer spriteRenderer;
    [SerializeField] private Sprite brokenSprite;
    [SerializeField] private float inertBackgroundThreshold;
    [SerializeField] private float inertForegroundThreshold;
    [SerializeField] private AudioSource audioSourcePrefab;
    [SerializeField] private RandomAudioClipTable hitClip;
    [SerializeField] private GameObject slashImpactPrefab;
    [SerializeField] private Rigidbody2D top;

    protected void Reset()
    {
	inertBackgroundThreshold = -1f;
	inertForegroundThreshold = -1f;
    }

    protected void Start()
    {
	float z = transform.position.z;
	if(z < inertBackgroundThreshold || z > inertForegroundThreshold)
	{
	    enabled = false;
	    return;
	}
    }

    public void Hit(HitInstance damageInstance)
    {
	int cardinalDirection = DirectionUtils.GetCardinalDirection(damageInstance.Direction);
	if (cardinalDirection != 2 && cardinalDirection != 0)
	{
	    return;
	}
	spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.g, spriteRenderer.color.b,0f);
	Transform transform = Instantiate(slashImpactPrefab).transform;
	transform.eulerAngles = new Vector3(0f, 0f, Random.Range(340f, 380f));
	Vector3 localScale = transform.localScale;
	localScale.x = ((cardinalDirection == 2) ? -1f : 1f);
	localScale.y = 1f;
	hitClip.SpawnAndPlayOneShot(audioSourcePrefab, base.transform.position);
	top.gameObject.SetActive(true);
	float num = (cardinalDirection == 2) ? Random.Range(120, 140) : Random.Range(40, 60);
	top.transform.localScale = new Vector3(localScale.x, localScale.y, top.transform.localScale.z);
	top.velocity = new Vector2(Mathf.Cos(num * 0.017453292f), Mathf.Sin(num * 0.017453292f)) * 5f;
	top.transform.Rotate(new Vector3(0f, 0f, num));
	base.enabled = false;
    }
}

以及一个ScriptableObejct来控制随机播放某个片段的脚本: 

using System;
using UnityEngine;

/// <summary>
/// 根据权重weight确定随机播放某些音乐片段的概率
/// </summary>

[CreateAssetMenu(fileName = "RandomAudioClipTable", menuName = "Hollow Knight/Random Audio Clip Table", order = -1000)]
public class RandomAudioClipTable : ScriptableObject
{
    [SerializeField] private RandomAudioClipTable.Option[] options;
    [SerializeField] private float pitchMin;
    [SerializeField] private float pitchMax;

    protected void Reset()
    {
	pitchMax = 1f;
	pitchMin = 1f;
    }

    public AudioClip SelectClip()
    {
	if (options.Length == 0)
	{
	    return null;
	}
	if (options.Length == 1)
	{
	    return options[0].Clip;
	}
	float num = 0f;
	for (int i = 0; i < options.Length; i++)
	{
	    RandomAudioClipTable.Option option = options[i];
	    num += option.Weight;
	}
	float num2 = UnityEngine.Random.Range(0f, num);
	float num3 = 0f;
	for (int j = 0; j < options.Length - 1; j++)
	{
	    RandomAudioClipTable.Option option2 = options[j];
	    num3 += option2.Weight;
	    if (num2 < num3)
	    {
		return option2.Clip;
	    }
	}
	return options[options.Length - 1].Clip;
    }

    public float SelectPitch()
    {
	if (Mathf.Approximately(pitchMin, pitchMax))
	{
	    return pitchMax;
	}
	return UnityEngine.Random.Range(pitchMin, pitchMax);
    }

    public void PlayOneShotUnsafe(AudioSource audioSource)
    {
	if (audioSource == null)
	{
	    return;
	}
	AudioClip audioClip = SelectClip();
	if (audioClip == null)
	{
	    return;
	}
	audioSource.pitch = SelectPitch();
	audioSource.PlayOneShot(audioClip);
    }

    [Serializable]
    private struct Option
    {
	public AudioClip Clip;
	[Range(1f, 10f)]
	public float Weight;
    }
}
using System;
using UnityEngine;

public static class RandomAudioClipTableExtensions
{
    public static void PlayOneShot(this RandomAudioClipTable table, AudioSource audioSource)
    {
	if (table == null)
	{
	    return;
	}
	table.PlayOneShotUnsafe(audioSource);
    }

    public static void SpawnAndPlayOneShot(this RandomAudioClipTable table, AudioSource prefab, Vector3 position)
    {
	if (table == null)
	{
	    return;
	}
	if (prefab == null)
	{
	    return;
	}
	AudioClip audioClip = table.SelectClip();
	if (audioClip == null)
	{
	    return;
	}
	//TODO:Object Pool
	AudioSource audioSource = GameObject.Instantiate(prefab).GetComponent<AudioSource>();
	audioSource.transform.position = position;
	audioSource.pitch = table.SelectPitch();
	audioSource.volume = 1f;
	audioSource.PlayOneShot(audioClip);
    }
}

 

 

我的设置如下: 然后就可以做成预制体批量生产了

 

别忘了这些石块也要设置好Collider2D和Layer为Terrain

这些生命水蓝花也可以添加一下动画系统:

 还有记得设置好Roof Colliders防止玩家卡在地图内部

  • 4.制作地图后期该做的事

制作完地图的大部分以后,我们还有一些工作要完成,首先是设置好玩家的位置让玩家看起来从悬崖上跳下来的:

然后是设置敌人的位置:

 再添加一个Audio Source的Wind Player全局音量播放:

我们还需要制作后处理系统,来让场景看起来勃勃生机,导入Unity自带的后处理系统Post Processing来到tk2d

Camera中,我们先随便设置一下:New一个Profile就有了

 

  • 5.制作地图之修复意想不到的Bug

我建立这个标题的原因是发现我的敌人Buzzer不会正常执行PlaymakerFSM,给我整半天才发现在游戏初期创建的一些脚本有大问题,所以我们来修改一下这些脚本:

首先是LineOfSightDetecotr的层级问题:应该使用LayerMask.GetMask("Terrain")

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

public class LineOfSightDetector : MonoBehaviour
{
    [SerializeField] private AlertRange[] alertRanges;
    private bool canSeeHero;

    public bool CanSeeHero
    {
	get
	{
	    return canSeeHero;
	}
    }

    protected void Awake()
    {
    }

    protected void Update()
    {
	bool flag = false;
	for (int i = 0; i < alertRanges.Length; i++)
	{
	    AlertRange alertRange = alertRanges[i];
	    if(!(alertRange == null) && alertRange.IsHeroInRange)
	    {
		flag = true;
	    }
	}
	if(alertRanges.Length != 0 && !flag)
	{
	    canSeeHero = false;
	    return;
	}
	HeroController instance = HeroController.instance;
	if(instance == null)
	{
	    canSeeHero = false;
	    return;
	}
	Vector2 vector = transform.position;
	Vector2 vector2 = instance.transform.position;
	Vector2 vector3 = vector2 - vector;
	if (Physics2D.Raycast(vector, vector3.normalized, vector3.magnitude, LayerMask.GetMask("Terrain")))
	{
	    canSeeHero = false;
	}
	else
	{
	    canSeeHero = true;
	}
	Debug.DrawLine(vector, vector2, canSeeHero ? Color.green : Color.yellow);
    }
}

然后是设置方向的playmaker自定义行为脚本FaceDirection.cs我忘记哪个地方漏了一个负号 

using System;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory("Enemy AI")]
    [Tooltip("Object will flip to face the direction it is moving on X Axis.")]
    public class FaceDirection : RigidBody2dActionBase
    {
	[RequiredField]
	[CheckForComponent(typeof(Rigidbody2D))]
	public FsmOwnerDefault gameObject;

	[Tooltip("Does the target's sprite face right?")]
	public FsmBool spriteFacesRight;
	public bool playNewAnimation;
	public FsmString newAnimationClip;
	public bool everyFrame;
	public bool pauseBetweenTurns; //是否播放转身动画时暂停
	public FsmFloat pauseTime;
	private FsmGameObject target;
	private tk2dSpriteAnimator _sprite;
	private float xScale;

	private float pauseTimer;

	public override void Reset()
	{
	    gameObject = null;
	    spriteFacesRight = false;
	    everyFrame = false;
	    playNewAnimation = false;
	    newAnimationClip = null;
	}

	public override void OnEnter()
	{
	    CacheRigidBody2d(Fsm.GetOwnerDefaultTarget(gameObject));
	    target = Fsm.GetOwnerDefaultTarget(gameObject);
	    _sprite = target.Value.GetComponent<tk2dSpriteAnimator>();
	    xScale = target.Value.transform.localScale.x;
	    if(xScale < 0f)
	    {
		xScale *= -1f;
	    }
	    DoFace();
	    if (!everyFrame)
	    {
		Finish();
	    }
	}

	public override void OnUpdate()
	{
	    DoFace();
	}

	private void DoFace()
	{
	    if (rb2d == null)
		return;
	    Vector2 velocity = rb2d.velocity;
	    Vector3 localScale = target.Value.transform.localScale; 
	    float x = velocity.x;
	    if(pauseTimer <= 0f || !pauseBetweenTurns)
	    {
		if(x > 0f)
		{
		    if (spriteFacesRight.Value)
		    {
			if(localScale.x != xScale)
			{
			    pauseTimer = pauseTime.Value;
			    localScale.x = xScale;
			    if (playNewAnimation)
			    {
				_sprite.Play(newAnimationClip.Value);
				_sprite.PlayFromFrame(0);
			    }
			}
		    }
		    else if(localScale.x != -xScale)
		    {
			pauseTimer = pauseTime.Value;
			localScale.x = -xScale;
			if (playNewAnimation)
			{
			    _sprite.Play(newAnimationClip.Value);
			    _sprite.PlayFromFrame(0);
			}
		    }
		}
		else if(x <= 0f)
		{
		    if (spriteFacesRight.Value)
		    {
			if (localScale.x != -xScale)
			{
			    pauseTimer = pauseTime.Value;
			    localScale.x = -xScale;
			    if (playNewAnimation)
			    {
				_sprite.Play(newAnimationClip.Value);
				_sprite.PlayFromFrame(0);
			    }
			}
		    }
		    else if (localScale.x != xScale)
		    {
			pauseTimer = pauseTime.Value;
			localScale.x = xScale;
			if (playNewAnimation)
			{
			    _sprite.Play(newAnimationClip.Value);
			    _sprite.PlayFromFrame(0);
			}
		    }
		}
	    }
	    else
	    {
		pauseTimer -= Time.deltaTime;//开始计时
	    }
	    target.Value.transform.localScale = new Vector3(localScale.x, target.Value.transform.localScale.y, target.Value.transform.localScale.z);
	}
    }
}

 然后是AlertRange.cs同样也是要用LayerMask.GetMask("Player")

using System;
using UnityEngine;

[RequireComponent(typeof(Collider2D))]
public class AlertRange : MonoBehaviour
{
    private bool isHeroInRange;
    private Collider2D[] colliders;

    public bool IsHeroInRange
    {
	get
	{
	    return isHeroInRange;
	}
    }

    protected void Awake()
    {
	colliders = GetComponents<Collider2D>();
    }

    protected void OnTriggerEnter2D(Collider2D collision)
    {
	isHeroInRange = true;
    }

    protected void OnTriggerExit2D(Collider2D collision)
    {
	if(colliders.Length <= 1 || !StillInColliders())
	{
	    isHeroInRange = false;
	}
    }

    private bool StillInColliders()
    {
	bool flag = false;
	foreach (Collider2D collider2D in colliders)
	{
	    if (collider2D is CircleCollider2D)
	    {
		CircleCollider2D circleCollider2D = (CircleCollider2D)collider2D;
		flag = Physics2D.OverlapCircle(transform.TransformPoint(circleCollider2D.offset), circleCollider2D.radius * Mathf.Max(transform.localScale.x, transform.localScale.y),LayerMask.GetMask("Player")) != null;
	    }
	    else if (collider2D is BoxCollider2D)
	    {
		BoxCollider2D boxCollider2D = (BoxCollider2D)collider2D;
		flag = Physics2D.OverlapBox(transform.TransformPoint(boxCollider2D.offset), new Vector2(boxCollider2D.size.x * transform.localScale.x, boxCollider2D.size.y * transform.localScale.y), transform.eulerAngles.z, LayerMask.GetMask("Player")) != null;
	    }
	    if (flag)
	    {
		break;
	    }
	}
	return flag;
    }

    public static AlertRange Find(GameObject root,string childName)
    {
	if (root == null)
	    return null;
	bool flag = !string.IsNullOrEmpty(childName);
	Transform transform = root.transform;
	for (int i = 0; i < transform.childCount; i++)
	{
	    Transform child = transform.GetChild(i);
	    AlertRange component = child.GetComponent<AlertRange>();
	    if(component != null && (!flag || !(child.gameObject.name != childName)))
	    {
		return component;
	    }
	}
	return null;
    }

}

然后FaceObject.cs行为好像也是漏了一个负号:

using System;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory("Enemy AI")]
    [Tooltip("Object A will flip to face Object B horizontally.")]
    public class FaceObject : FsmStateAction
    {
	//A朝着B看
	[RequiredField]
	public FsmGameObject objectA;
	[RequiredField]
	[UIHint(UIHint.Variable)]
	public FsmGameObject objectB;
	[Tooltip("Does object A's sprite face right?")]
	public FsmBool spriteFacesRight;
	public bool playNewAnimation;
	public FsmString newAnimationClip;
	public bool resetFrame;
	[Tooltip("Repeat every frame.")]
	public bool everyFrame;
	private float xScale;
	private FsmVector3 vector;
	private tk2dSpriteAnimator _sprite;

	public override void Reset()
	{
	    objectA = null;
	    objectB = null;
	    newAnimationClip = null;
	    spriteFacesRight = false;
	    everyFrame = false;
	    resetFrame = false;
	    playNewAnimation = false;
	}

	public override void OnEnter()
	{
	    _sprite = objectA.Value.GetComponent<tk2dSpriteAnimator>();
	    if (_sprite == null)
	    {
		Finish();
	    }
	    xScale = objectA.Value.transform.localScale.x; //xsclae= 1f;
	    if (xScale < 0f)
	    {
		xScale *= -1f;
	    }
	    DoFace();
	    if (!everyFrame)
	    {
		Finish();
	    }
	}

	public override void OnUpdate()
	{
	    DoFace();
	}

	private void DoFace()
	{
	    Vector3 localScale = objectA.Value.transform.localScale;
	    if(objectB.Value == null || objectB.IsNone)
	    {
		Finish();
	    }
	    if (objectA.Value.transform.position.x < objectB.Value.transform.position.x) //B在A的右边,A向右看
	    {
		if (spriteFacesRight.Value)
		{
		    if (localScale.x != xScale)
		    {
			localScale.x = xScale;
			if (resetFrame)
			{
			    _sprite.PlayFromFrame(0);
			}
			if (playNewAnimation)
			{
			    _sprite.Play(newAnimationClip.Value);
			}
		    }
		}
		else if (localScale.x != -xScale)
		{
		    localScale.x = -xScale;
		    if (resetFrame)
		    {
			_sprite.PlayFromFrame(0);
		    }
		    if (playNewAnimation)
		    {
			_sprite.Play(newAnimationClip.Value);
		    }
		}
	    }
	    else if (spriteFacesRight.Value)
	    {
		if (localScale.x != -xScale)
		{
		    localScale.x = -xScale;
		    if (resetFrame)
		    {
			_sprite.PlayFromFrame(0);
		    }
		    if (playNewAnimation)
		    {
			_sprite.Play(newAnimationClip.Value);
		    }
		}
	    }
	    else if (localScale.x != xScale)//B在A的左边,A向左看
	    {
		localScale.x = xScale;
		if (resetFrame)
		{
		    _sprite.PlayFromFrame(0);
		}
		if (playNewAnimation)
		{
		    _sprite.Play(newAnimationClip.Value);
		}
	    }
	    objectA.Value.transform.localScale = new Vector3(localScale.x, objectA.Value.transform.localScale.y, objectA.Value.transform.localScale.z);
	}
    }

}

还有就是buzzer 的蚊子动画系统tk2dSpriteAnimation:得用Loop Secetion的Wrap Mode

 运行一下,发现没有问题就OK了,

至此我们制作了一张完整的地图,但还有一些可交互的物体的显示设置要完成。 

二、制作摄像机系统

1.制作相机跟随玩家系统

说到摄像机系统,我本来是想用Cinemachine和Pro Camera2D的,但我学到了一个方法叫Vector3.SmoothDamp,效果非常好,于是我决定自己来做一个摄像机系统:

首先为我们的tk2dCamera添加一个CameraController.cs摄像机控制器系统

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

public class CameraController : MonoBehaviour
{
    private bool verboseMode = true;

    public CameraMode mode;
    private CameraMode prevMode;

    public bool atSceneBounds;//是否位于场景的边界外
    public bool atHorizontalSceneBounds;//是否位于场景的X轴方向的边界外

    public tk2dTileMap tilemap; //通过tk2dTileMap的高度和宽度来确定场景的宽度和长度以及摄像机限制
    public float sceneWidth; //场景的高度
    public float sceneHeight; //场景的宽度
    public float xLimit;
    public float yLimit;

    public Vector3 destination;
    private float targetDeltaX;
    private float targetDeltaY;

    public float lookOffset;

    private Vector3 velocity;
    private Vector3 velocityX;
    private Vector3 velocityY;
    private float maxVelocityCurrent;
    public float maxVelocity;

    public float dampTime;
    public float dampTimeX;
    public float dampTimeY;

    private Camera cam;
    public CameraTarget camTarget;
    private GameManager gm;
    private HeroController hero_ctrl;
    private Transform cameraParent;


    private void Awake()
    {
	GameInit();
	SceneInit();
    }

    private void LateUpdate()
    {
	float x = transform.position.x;
	float y = transform.position.y;
	float z = transform.position.z;
	float x2 = cameraParent.position.x;
	float y2 = cameraParent.position.y;
	if(mode != CameraMode.FROZEN)
	{
	    lookOffset = 0f;
	    UpdateTargetDestinationDelta();
	    Vector3 vector = cam.WorldToViewportPoint(camTarget.transform.position);
	    Vector3 vector2 = new Vector3(targetDeltaX, targetDeltaY, 0f) - cam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, vector.z));
	    destination = new Vector3(x + vector2.x, y + vector2.y, z);
	    if(mode == CameraMode.LOCKED)
	    {
		if(lookOffset > 0f && currentLockArea.preventLookUp && destination.y > currentLockArea.cameraYMax)
		{
		    if (transform.position.y > currentLockArea.cameraYMax)
		    {
			destination = new Vector3(destination.x, destination.y - lookOffset, destination.z);
		    }
		    else
		    {
			destination = new Vector3(destination.x, currentLockArea.cameraYMax, destination.z);
		    }
		}
		if(lookOffset < 0f && currentLockArea.preventLookDown && destination.y < currentLockArea.cameraYMin)
		{
		    if (transform.position.y < currentLockArea.cameraYMin)
		    {
			destination = new Vector3(destination.x, destination.y - lookOffset, destination.z);
		    }
		    else
		    {
			destination = new Vector3(destination.x, currentLockArea.cameraYMin, destination.z);
		    }
		}
	    }
	    if(mode == CameraMode.FOLLOWING || mode == CameraMode.LOCKED)
	    {
		    destination = KeepWithinSceneBounds(destination);
	    }
	        Vector3 vector3 = Vector3.SmoothDamp(transform.position, new Vector3(destination.x, y, z), ref velocityX, dampTimeX);
	        Vector3 vector4 = Vector3.SmoothDamp(transform.position, new Vector3(x, destination.y, z), ref velocityY, dampTimeY);
	    transform.SetPosition2D(vector3.x, vector4.y);
	    x = transform.position.x;
	    y = transform.position.y;
	    if(velocity.magnitude > maxVelocityCurrent)
	    {
		velocity = velocity.normalized * maxVelocityCurrent;
	    }
	}
	if (x + x2 < 14.6f)
	{
	    transform.SetPositionX(14.6f);
	}
	if (transform.position.x + x2 > xLimit)
	{
	    transform.SetPositionX(xLimit);
	}
	if (transform.position.y + y2 < 8.3f)
	{
	    transform.SetPositionY(8.3f);
	}
	if (transform.position.y + y2 > yLimit)
	{
	    transform.SetPositionY(yLimit);
	}
	if (startLockedTimer > 0f)
	{
	    startLockedTimer -= Time.deltaTime;
	}
    }

    public void GameInit()
    {
	    gm = GameManager.instance;
	    cam = GetComponent<Camera>();
	    cameraParent = transform.parent.transform;
    }

    public void SceneInit()
    {
	    velocity = Vector3.zero;
	    if(hero_ctrl == null)
	    {
	        hero_ctrl = HeroController.instance;
	        hero_ctrl.heroInPosition += PositionToHero;
	    }
	GetTilemapInfo();

	dampTimeX = dampTime;
	dampTimeY = dampTime;
	maxVelocityCurrent = maxVelocity;

    }

    public void PositionToHero(bool forceDirect)
    {
	    StartCoroutine(DoPositionToHero(forceDirect));
    }

    private IEnumerator DoPositionToHero(bool forceDirect)
    {
	    yield return new WaitForFixedUpdate();
	    GetTilemapInfo();
	    camTarget.PositionToStart();
	    CameraMode previousMode = mode;
	    SetMode(CameraMode.FROZEN);

	Vector3 newPosition = KeepWithinSceneBounds(camTarget.transform.position);
	if (verboseMode)
	{
	    Debug.LogFormat("CC - STR: NewPosition: {0} TargetDelta: ({1}, {2}) CT-XOffset: {3} HeroPos: {4} CT-Pos: {5}", new object[]
	    {
		newPosition,
		targetDeltaX,
		targetDeltaY,
		camTarget.xOffset,
		hero_ctrl.transform.position,
		camTarget.transform.position
	    });
	}
	if (forceDirect)
	{
	    if (verboseMode)
	    {
		Debug.Log("====> TEST 1a - ForceDirect Positioning Mode");
	    }
	    transform.SetPosition2D(newPosition);
	}
	else
	{
	    bool flag2;
	    bool flag = IsAtHorizontalSceneBounds(newPosition, out flag2);
	    bool flag3 = false;
	    if(currentLockArea != null)
	    {
		flag3 = true;
	    }
	    if (flag3)
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 3 - Lock Zone Active");
		}
		PositionToHeroFacing(newPosition, true);
		transform.SetPosition2D(KeepWithinSceneBounds(transform.position));
	    }
	    else
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 4 - No Lock Zone");
		}
		PositionToHeroFacing(newPosition, false);
	    }
	    if (flag)
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 2 - At Horizontal Scene Bounds");
		}
		if ((flag2 && !hero_ctrl.cState.facingRight) || (!flag2 && hero_ctrl.cState.facingRight))
		{
		    if (verboseMode)
		    {
			Debug.Log("====> TEST 2a - Hero Facing Bounds");
		    }
		    transform.SetPosition2D(newPosition);
		}
		else
		{
		    if (verboseMode)
		    {
			Debug.Log("====> TEST 2b - Hero Facing Inwards");
		    }
		    if (IsTouchingSides(targetDeltaX))
		    {
			if (verboseMode)
			{
			    Debug.Log("Xoffset still touching sides");
			}
			transform.SetPosition2D(newPosition);
		    }
		    else
		    {
			if (verboseMode)
			{
			    Debug.LogFormat("Not Touching Sides with Xoffset CT: {0} Hero: {1}", new object[]
			    {
				camTarget.transform.position,
				hero_ctrl.transform.position
			    });
			}
			if (hero_ctrl.cState.facingRight)
			{
			    transform.SetPosition2D(hero_ctrl.transform.position.x + 1f, newPosition.y);
			}
			else
			{
			    transform.SetPosition2D(hero_ctrl.transform.position.x - 1f, newPosition.y);
			}
		    }
		}
	    }
	}
	destination = transform.position;
	velocity = Vector3.zero;
	velocityX = Vector3.zero;
	velocityY = Vector3.zero;
	yield return new WaitForSeconds(0.1f);

	if(previousMode == CameraMode.FROZEN)
	{
	    SetMode(CameraMode.FOLLOWING);
	}
	else if(previousMode == CameraMode.LOCKED)
	{
	    if (currentLockArea != null)
	    {
		SetMode(previousMode);
	    }
	    else
	    {
		SetMode(CameraMode.FOLLOWING);
	    }
	}
	else
	{
	    SetMode(previousMode);
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CC - PositionToHero FIN: - TargetDelta: ({0}, {1}) Destination: {2} CT-XOffset: {3} NewPosition: {4} CamTargetPos: {5} HeroPos: {6}", new object[]
	    {
		targetDeltaX,
		targetDeltaY,
		destination,
		camTarget.xOffset,
		newPosition,
		camTarget.transform.position,
		hero_ctrl.transform.position
	    });
	}
    }

    private void PositionToHeroFacing(Vector3 newPosition, bool useXOffset)
    {
	if (useXOffset)
	{
	    transform.SetPosition2D(newPosition.x + camTarget.xOffset, newPosition.y);
	    return;
	}
	if (hero_ctrl.cState.facingRight)
	{
	    transform.SetPosition2D(newPosition.x + 1f, newPosition.y);
	    return;
	}
	transform.SetPosition2D(newPosition.x - 1f, newPosition.y);
    }

    private void GetTilemapInfo()
    {
	    tilemap = gm.tilemap;
	    sceneWidth = tilemap.width;
	    sceneHeight = tilemap.height;
	    xLimit = sceneWidth - 14.6f;
	    yLimit = sceneHeight - 8.3f;
    }

    private void UpdateTargetDestinationDelta()
    {
	targetDeltaX = camTarget.transform.position.x + camTarget.xOffset + camTarget.dashOffset;
	targetDeltaY = camTarget.transform.position.y + camTarget.fallOffset + lookOffset;
    }

    private bool IsAtHorizontalSceneBounds(Vector2 targetDest, out bool leftSide)
    {
	bool result = false;
	leftSide = false;
	if (targetDest.x <= 14.6f)
	{
	    result = true;
	    leftSide = true;
	}
	if (targetDest.x >= xLimit)
	{
	    result = true;
	    leftSide = false;
	}
	return result;
    }

    public Vector3 KeepWithinSceneBounds(Vector3 targetDest)
    {
	Vector3 vector = targetDest;
	bool flag = false;
	bool flag2 = false;
	if (vector.x < 14.6f)
	{
	    vector = new Vector3(14.6f, vector.y, vector.z);
	    flag = true;
	    flag2 = true;
	}
	if (vector.x > xLimit)
	{
	    vector = new Vector3(xLimit, vector.y, vector.z);
	    flag = true;
	    flag2 = true;
	}
	if (vector.y < 8.3f)
	{
	    vector = new Vector3(vector.x, 8.3f, vector.z);
	    flag = true;
	}
	if (vector.y > yLimit)
	{
	    vector = new Vector3(vector.x, yLimit, vector.z);
	    flag = true;
	}
	atSceneBounds = flag;
	atHorizontalSceneBounds = flag2;
	return vector;
    }

    private bool IsTouchingSides(float x)
    {
	bool result = false;
	if (x <= 14.6f)
	{
	    result = true;
	}
	if (x >= xLimit)
	{
	    result = true;
	}
	return result;
    }

    public void SetMode(CameraMode newMode)
    {
	if (newMode != mode)
	{
	    if (newMode == CameraMode.PREVIOUS)
	    {
		mode = prevMode;
		return;
	    }
	    prevMode = mode;
	    mode = newMode;
	}
    }

    public enum CameraMode
    {
	FROZEN,
	FOLLOWING,
	LOCKED,
	PANNING,
	FADEOUT,
	FADEIN,
	PREVIOUS
    }
}

来到GameManager.cs中,我们来获取当前场景的tilemap:

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

public class GameManager : MonoBehaviour
{
    public bool isPaused;

    private int timeSlowedCount;
    public bool TimeSlowed
    {
	get
	{
	    return timeSlowedCount > 0;
	}
    }

    [SerializeField] public PlayerData playerData;
    public CameraController cameraCtrl { get; private set; }
    private GameCameras gameCams;

    public string sceneName; //场景名字
    public float sceneWidth;//场景宽度
    public float sceneHeight;//场景高度
    public tk2dTileMap tilemap{ get; private set; }
    private static readonly string[] SubSceneNameSuffixes = new string[]
	{
		"_boss_defeated",
		"_boss",
		"_preload"
	};


    private static GameManager _instance;
    public static GameManager instance
    {
	get
	{
	    if(_instance == null)
	    {
		_instance = FindObjectOfType<GameManager>();
	    }
	    if (_instance == null)
	    {
		Debug.LogError("Couldn't find a Game Manager, make sure one exists in the scene.");
	    }
	    else if (Application.isPlaying)
	    {
		DontDestroyOnLoad(_instance.gameObject);
	    }
	    return _instance;
	}
    }

    private void Awake()
    {
	if(_instance != this)
	{
	    _instance = this;
	    DontDestroyOnLoad(this);
	    SetupGameRefs();
	    SetupSceneRefs();
	    return;
	}
	if(this != _instance)
	{
	    Destroy(gameObject);
	    return;
	}
	SetupGameRefs();
	SetupSceneRefs();
    }

    private void SetupGameRefs()
    {
	playerData = PlayerData.instance;
	gameCams = GameCameras.instance;
	cameraCtrl = gameCams.cameraController;
    }

    public void SetupSceneRefs()
    {
	UpdateSceneName();
	RefreshTilemapInfo(sceneName);
    }

    private void UpdateSceneName()
    {
	sceneName = GetBaseSceneName(SceneManager.GetActiveScene().name);
    }

    /// <summary>
    /// 获取场景的基础名字
    /// </summary>
    /// <param name="fullSceneName"></param>
    /// <returns></returns>
    public static string GetBaseSceneName(string fullSceneName)
    {
	for (int i = 0; i < SubSceneNameSuffixes.Length; i++)
	{
	    string text = SubSceneNameSuffixes[i];
	    if (fullSceneName.EndsWith(text, StringComparison.InvariantCultureIgnoreCase))
	    {
		return fullSceneName.Substring(0, fullSceneName.Length - text.Length);
	    }
	}
	return fullSceneName;
    }

    /// <summary>
    /// 重新刷新场景的tilemap的信息
    /// </summary>
    /// <param name="targetScene"></param>
    public void RefreshTilemapInfo(string targetScene)
    {
	tk2dTileMap tk2dTileMap = null;
	GameObject gameObject = GameObject.Find("TileMap");
	if(gameObject != null)
	{
	    tk2dTileMap = GetTileMap(gameObject);
	}
	if (tk2dTileMap == null)
	{
	    Debug.LogErrorFormat("Failed to find tilemap in {0} entirely.", new object[]
	    {
		targetScene
	    });
	    return;
	}
	tilemap = tk2dTileMap;
	sceneWidth = tilemap.width;
	sceneHeight = tilemap.height;
    }

    private static tk2dTileMap GetTileMap(GameObject gameObject)
    {
	if (gameObject.CompareTag("TileMap"))
	{
	    return gameObject.GetComponent<tk2dTileMap>();
	}
	return null;
    }

    public int GetPlayerDataInt(string intName)
    {
	return playerData.GetInt(intName);
    }

    public bool GetPlayerDataBool(string boolName)
    {
	return playerData.GetBool(boolName);
    }

    public void SetPlayerDataBool(string boolName,bool value)
    {
	playerData.SetBool(boolName, value); 
    }

    private IEnumerator SetTimeScale(float newTimeScale,float duration)
    {
	float lastTimeScale = TimeController.GenericTimeScale;
	for (float timer = 0f; timer < duration; timer += Time.unscaledDeltaTime)
	{
	    float t = Mathf.Clamp01(timer / duration);
	    SetTimeScale(Mathf.Lerp(lastTimeScale, newTimeScale, t));
	    yield return null;
	}
	SetTimeScale(newTimeScale);
    }

    private void SetTimeScale(float newTimeScale)
    {
	if(timeSlowedCount > 1)
	{
	    newTimeScale = Mathf.Min(newTimeScale, TimeController.GenericTimeScale);
	}
	TimeController.GenericTimeScale = ((newTimeScale > 0.01f) ? newTimeScale : 0f);
    }

    public IEnumerator FreezeMoment(float rampDownTime,float waitTime,float rampUpTime,float targetSpeed)
    {
	timeSlowedCount++;
	yield return StartCoroutine(SetTimeScale(targetSpeed, rampDownTime));
	for (float timer = 0f; timer < waitTime; timer += Time.unscaledDeltaTime)
	{
	    yield return null;
	}
	yield return StartCoroutine(SetTimeScale(1f, rampUpTime));
	timeSlowedCount--;
    }
}

”别忘了给场景中的Tilemap添加同名标签,不然gamemanager识别不到

 然后我打算创建一个新的游戏对象CameraTarget用来替代相机跟随的玩家对象,也就是说,让CameraTarget和位置和小骑士的位置相同,然后CameraController跟随CameraTarget,别问我为什么这么做,因为我试着让CameraController跟随小骑士差点给我晃晕了

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

public class CameraTarget : MonoBehaviour
{
    private bool verboseMode = true;
    public bool isGameplayScene;

    private float slowTimer;
    public float slowTime = 0.5f;
    private float dampTimeX;
    private float dampTimeY;
    public float dampTimeSlow;
    public float dampTimeNormal;

    private float snapDistance = 0.15f;

    private Vector3 heroPrevPosition;
    public Vector3 destination;
    public Vector3 velocityX;
    public Vector3 velocityY;

    public float xOffset; //相机X轴方向上的偏移量
    public float xLookAhead; //相机X轴方向上的提前量

    public float dashOffset; //冲刺时的相机偏移量
    public float fallOffset; //下落时相机的偏移量
    public float dashLookAhead;//冲刺时相机的提前量

    [HideInInspector]
    public GameManager gm;
    [HideInInspector]
    public HeroController hero_ctrl;
    private Transform heroTransform;

    public CameraController cameraCtrl;
    public TargetMode mode;

    public bool stickToHeroX;
    public bool stickToHeroY;
    public bool fallStick;
    public float fallCatcher;

    private void Awake()
    {
	    GameInit();
	    SceneInit();
	
    }

    public void GameInit()
    {
	    gm = GameManager.instance;
	    if(cameraCtrl == null)
	    {
	        cameraCtrl = GetComponentInParent<CameraController>();
	    }
    }

    public void SceneInit()
    {
	if(gm == null)
	{
	    gm = GameManager.instance;
	}
	isGameplayScene = true;
	hero_ctrl = HeroController.instance;
	heroTransform = hero_ctrl.transform;
	mode = TargetMode.FOLLOW_HERO;
	stickToHeroX = true;
	stickToHeroY = true;
	fallCatcher = 0f;
    }

    private void Update()
    {
	if(hero_ctrl == null || !isGameplayScene)
	{
	    mode = TargetMode.FREE;
	    return;
	}
	if (isGameplayScene)
	{
	    float num = transform.position.x;
	    float num2 = transform.position.y;
	    float z = transform.position.z;
	    float x = heroTransform.position.x;
	    float y = heroTransform.position.y;
	    Vector3 position = heroTransform.position;
	    if(mode == TargetMode.FOLLOW_HERO)
	    {
		SetDampTime();
		destination = heroTransform.position;
		if (!fallStick && fallCatcher <= 0f)
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, transform.position.y, z), ref velocityX, dampTimeX).x, Vector3.SmoothDamp(transform.position, new Vector3(transform.position.x, destination.y, z), ref velocityY, dampTimeY).y, z);
		}
		else
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, transform.position.y, z), ref velocityX, dampTimeX).x, transform.position.y, z);
		}
		num = transform.position.x;
		num2 = transform.position.y;
		z = transform.position.z;
		if ((heroPrevPosition.x < num && x > num) || (heroPrevPosition.x > num && x < num) || (num >= x - snapDistance && num <= x + snapDistance))
		{
		    stickToHeroX = true;
		}
		if ((heroPrevPosition.y < num2 && y > num2) || (heroPrevPosition.y > num2 && y < num2) || (num2 >= y - snapDistance && num2 <= y + snapDistance))
		{
		    stickToHeroY = true;
		}
		if (stickToHeroX)
		{
		    transform.SetPositionX(x);
		    num = x;
		}
		if (stickToHeroY)
		{
		    transform.SetPositionY(y);
		    num2 = y;
		}
	    }
	    if(mode == TargetMode.LOCK_ZONE)
	    {
		    SetDampTime();
		    destination = heroTransform.position;
		if (!fallStick && fallCatcher <= 0f)
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, num2, z), ref velocityX, dampTimeX).x, Vector3.SmoothDamp(transform.position, new Vector3(num, destination.y, z), ref velocityY, dampTimeY).y, z);
		}
		else
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, num2, z), ref velocityX, dampTimeX).x, num2, z);
		}
		num = transform.position.x;
		num2 = transform.position.y;
		z = transform.position.z;
		if ((heroPrevPosition.x < num && x > num) || (heroPrevPosition.x > num && x < num) || (num >= x - snapDistance && num <= x + snapDistance))
		{
		    stickToHeroX = true;
		}
		if ((heroPrevPosition.y < num2 && y > num2) || (heroPrevPosition.y > num2 && y < num2) || (num2 >= y - snapDistance && num2 <= y + snapDistance))
		{
		    stickToHeroY = true;
		}
		if (stickToHeroX)
		{
		    
		}
		if (stickToHeroY)
		{
		}
	    }
	    if(hero_ctrl != null)
	    {
		if (hero_ctrl.cState.facingRight)
		{
		    if(xOffset < xLookAhead)
		    {
			xOffset += Time.deltaTime * 6f;
		    }
		}
		else if (xOffset > -xLookAhead)
		{
		    xOffset -= Time.deltaTime * 6f;
		}
		if(xOffset < -xLookAhead)
		{
		    xOffset = -xLookAhead;
		}
		if (xOffset > xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if(mode == TargetMode.LOCK_ZONE)
		{
		 
		}
		if (xOffset < -xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if (xOffset > xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if (hero_ctrl.cState.dashing && (hero_ctrl.current_velocity.x > 5f || hero_ctrl.current_velocity.x < -5f))
		{
		    if (hero_ctrl.cState.facingRight)
		    {
			dashOffset = dashLookAhead;
		    }
		    else
		    {
			dashOffset = -dashLookAhead;
		    }
		    if (mode == TargetMode.LOCK_ZONE)
		    {
			if (num + dashOffset > xLockMax)
			{
			    dashOffset = 0f;
			}
			if (num + dashOffset < xLockMin)
			{
			    dashOffset = 0f;
			}
			if (x > xLockMax || x < xLockMin)
			{
			    dashOffset = 0f;
			}
		    }
		}
		else
		{
		    dashOffset = 0f;
		}
		heroPrevPosition = heroTransform.position;
	    }
	    if(hero_ctrl != null && !hero_ctrl.cState.falling)
	    {
		fallCatcher = 0f;
		fallStick = false;
	    }
	    if (mode == TargetMode.FOLLOW_HERO || mode == TargetMode.LOCK_ZONE)
	    {
		if (hero_ctrl.cState.falling && cameraCtrl.transform.position.y > y + 0.1f && !fallStick && (cameraCtrl.transform.position.y - 0.1f >= yLockMin || mode != TargetMode.LOCK_ZONE))
		{
		    Debug.LogFormat("Fall Catcher");								    
		    cameraCtrl.transform.SetPositionY(cameraCtrl.transform.position.y - fallCatcher * Time.deltaTime);
		    if (mode == TargetMode.LOCK_ZONE && cameraCtrl.transform.position.y < yLockMin)
		    {
			cameraCtrl.transform.SetPositionY(yLockMin);
		    }
		    if (cameraCtrl.transform.position.y < 8.3f)
		    {
			cameraCtrl.transform.SetPositionY(8.3f);
		    }
		    if (fallCatcher < 25f)
		    {
			fallCatcher += 80f * Time.deltaTime;
		    }
		    if (cameraCtrl.transform.position.y < heroTransform.position.y + 0.1f)
		    {
			fallStick = true;
		    }
		    transform.SetPositionY(cameraCtrl.transform.position.y);
		    num2 = cameraCtrl.transform.position.y;
		}
		if (fallStick)
		{
		    fallCatcher = 0f;
		    if (heroTransform.position.y + 0.1f >= yLockMin || mode != TargetMode.LOCK_ZONE)
		    {
			Debug.LogFormat("将cameraCtrl的Y坐标设置成heroTransform,再将cameraTarget的Y坐标设置成cameraCtrl的Y坐标");
			cameraCtrl.transform.SetPositionY(heroTransform.position.y + 0.1f);
			transform.SetPositionY(cameraCtrl.transform.position.y);
			num2 = cameraCtrl.transform.position.y;
		    }
		    if (mode == TargetMode.LOCK_ZONE && cameraCtrl.transform.position.y < yLockMin)
		    {
			cameraCtrl.transform.SetPositionY(yLockMin);
		    }
		    if (cameraCtrl.transform.position.y < 8.3f)
		    {
			cameraCtrl.transform.SetPositionY(8.3f);
		    }
		}
	    }
	}
    }

    public void PositionToStart()
    {
	float x = transform.position.x;
	Vector3 position = transform.position;
	float x2 = heroTransform.position.x;
	float y = heroTransform.position.y;
	velocityX = Vector3.zero;
	velocityY = Vector3.zero;
	destination = heroTransform.position;
	if (hero_ctrl.cState.facingRight)
	{
	    xOffset = 1f;
	}
	else
	{
	    xOffset = -1f;
	}
	if (mode == TargetMode.LOCK_ZONE)
	{
	   
	}
	if (xOffset < -xLookAhead)
	{
	    xOffset = -xLookAhead;
	}
	if (xOffset > xLookAhead)
	{
	    xOffset = xLookAhead;
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CT PTS - xOffset: {0} HeroPos: {1}, {2}", new object[]
	    {
		xOffset,
		x2,
		y
	    });
	}
	if (mode == TargetMode.FOLLOW_HERO)
	{
	    if (verboseMode)
	    {
		Debug.LogFormat("CT PTS - Follow Hero - CT Pos: {0}", new object[]
		{
		    base.transform.position
		});
	    }
	    transform.position = cameraCtrl.KeepWithinSceneBounds(destination);
	}
	else if (mode == TargetMode.LOCK_ZONE)
	{
	    
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CT - PTS: HeroPos: {0} Mode: {1} Dest: {2}", new object[]
	    {
				heroTransform.position,
				mode,
				destination
	    });
	}
	heroPrevPosition = heroTransform.position;
    }

    public enum TargetMode
    {
	    FOLLOW_HERO,
	    LOCK_ZONE,
	    BOSS,
	    FREE
    }
}

后面我们会做一个只负责UI的HudCamera,所以我们需要一个整体的对象来统筹这两个相机,所以就有了我们的GameCameras.cs:

using System;
using UnityEngine;
using UnityEngine.UI;

public class GameCameras : MonoBehaviour
{
    private static GameCameras _instance;
    public static GameCameras instance
    {
	get
	{
	    if (_instance == null)
	    {
		_instance = FindObjectOfType<GameCameras>();
		if (_instance == null)
		{
		    Debug.LogError("Couldn't find GameCameras, make sure one exists in the scene.");
		}
		DontDestroyOnLoad(_instance.gameObject);
	    }
	    return _instance;
	}
    }

    [Header("Controllers")]
    public CameraController cameraController;
    public CameraTarget cameraTarget;

    private GameManager gm;

    private void Awake()
    {
	if(_instance == null)
	{
	    _instance = this;
	    DontDestroyOnLoad(this);
	    return;
	}
	if(this != _instance)
	{
	    DestroyImmediate(gameObject);
	    return;
	}
    }

    private void Start()
    {
	SetupGameRefs();
    }

    private void SetupGameRefs()
    {
	gm = GameManager.instance;
	if (cameraTarget != null)
	{
	    cameraTarget.GameInit();
	}
    }
}

 记得添加好组件:

2.制作相机视角锁定系统

然后就是相机锁定系统,如果你玩了空洞骑士一段时间,可能会注意到玩家在一些区域时不能按lookUp和lookDown的,这是因为你再往下看就看到地图之外的空白了,同时在这个区域的时候可能跳跃的时候摄像机移动思路不同,所以我们先添加上这些区域吧:

同时再给它们一个脚本CameraLockArea.cs:

using System;
using System.Collections;
using GlobalEnums;
using UnityEngine;
using UnityEngine.SceneManagement;
public class CameraLockArea : MonoBehaviour
{
    private bool verboseMode = true;
    public bool maxPriority;

    public float cameraXMin;
    public float cameraXMax;
    public float cameraYMin;
    public float cameraYMax;

    private Vector3 heroPos;

    private float leftSideX;
    private float rightSideX;
    private float topSideY;
    private float botSideY;

    public bool preventLookDown;
    public bool preventLookUp;

    private GameCameras gcams;
    private CameraController cameraCtrl;
    private CameraTarget camTarget;
    private Collider2D box2d;

    private void Awake()
    {
	box2d = GetComponent<Collider2D>();
    }

    private IEnumerator Start()
    {
	gcams = GameCameras.instance;
	cameraCtrl = gcams.cameraController;
	camTarget = gcams.cameraTarget;
	Scene scene = gameObject.scene;
	while (cameraCtrl.tilemap == null ||  cameraCtrl.tilemap.gameObject.scene != scene)
	{
	    yield return null;
	}
	if (!ValidateBounds())
	{
	    Debug.LogError("Camera bounds are unspecified for " + name + ", please specify lock area bounds for this Camera Lock Area.");
	}
	if(box2d != null)
	{
	    leftSideX = box2d.bounds.min.x;
	    rightSideX = box2d.bounds.max.x;
	    topSideY = box2d.bounds.max.y;
	    botSideY = box2d.bounds.min.y;
	}
    }

    private void OnTriggerEnter2D(Collider2D otherCollider)
    {
	if(otherCollider.tag == "Player")
	{
	    heroPos = otherCollider.gameObject.transform.position;
	    if(box2d != null)
	    {
		if(heroPos.x > leftSideX - 1f && heroPos.x < leftSideX + 1f)
		{
		    camTarget.enteredLeft = true; //从左边进来的
		}
		else
		{
		    camTarget.enteredLeft = false;
		}
		if (heroPos.x > rightSideX - 1f && heroPos.x < rightSideX + 1f)
		{
		    camTarget.enteredRight = true; //从Right边进来的
		}
		else
		{
		    camTarget.enteredRight = false;
		}
		if (heroPos.y > topSideY - 2f && heroPos.y < topSideY + 2f)
		{
		    camTarget.enteredTop = true; //从上边进来的
		}
		else
		{
		    camTarget.enteredTop = false;
		}
		if (heroPos.y > botSideY - 1f && heroPos.y < botSideY + 1f)
		{
		    camTarget.enteredBot = true; //从下边进来的
		}
		else
		{
		    camTarget.enteredBot = false;
		}
	    }
	    cameraCtrl.LockToArea(this);
	    if (verboseMode)
	    {
		Debug.Log("Lockzone Enter Lock " + name);
	    }
	}
    }

    private void OnTriggerStay2D(Collider2D otherCollider)
    {
	if(!isActiveAndEnabled || !box2d.isActiveAndEnabled)
	{
	    Debug.LogWarning("Fix for Unity trigger event queue!");
	    return;
	}
	if(otherCollider.tag == "Player")
	{
	    if (verboseMode)
	    {
		Debug.Log("Lockzone Stay Lock " + name);
	    }
	    cameraCtrl.LockToArea(this);
	}
    }

    private void OnTriggerExit2D(Collider2D otherCollider)
    {
	if (otherCollider.tag == "Player")
	{
	    heroPos = otherCollider.gameObject.transform.position;
	    if (box2d != null)
	    {
		if (heroPos.x > leftSideX - 1f && heroPos.x < leftSideX + 1f)
		{
		    camTarget.exitedLeft = true; //从左边离开的
		}
		else
		{
		    camTarget.exitedLeft = false;
		}
		if (heroPos.x > rightSideX - 1f && heroPos.x < rightSideX + 1f)
		{
		    camTarget.exitedRight = true; //从右边离开的
		}
		else
		{
		    camTarget.exitedRight = false;
		}
		if (heroPos.y > topSideY - 2f && heroPos.y < topSideY + 2f)
		{
		    camTarget.exitedTop = true; //从上边离开的
		}
		else
		{
		    camTarget.exitedTop = false;
		}
		if (heroPos.y > botSideY - 1f && heroPos.y < botSideY + 1f)
		{
		    camTarget.exitedBot = true; //从下边离开的
		}
		else
		{
		    camTarget.exitedBot = false;
		}
	    }
	    cameraCtrl.ReleaseLock(this);
	    if (verboseMode)
	    {
		Debug.Log("Lockzone Exit Lock " + name);
	    }
	}
    }

    public void OnDisable()
    {
	if(cameraCtrl != null)
	{
	    cameraCtrl.ReleaseLock(this);
	}
    }

    /// <summary>
    /// 激活摄像机的边界添加到自己身上
    /// </summary>
    /// <returns></returns>
    private bool ValidateBounds()
    {
	if (cameraXMin == -1f)
	{
	    cameraXMin = 14.6f;
	}
	if (cameraXMax == -1f)
	{
	    cameraXMax = cameraCtrl.xLimit;
	}
	if (cameraYMin == -1f)
	{
	    cameraYMin = 8.3f;
	}
	if (cameraYMax == -1f)
	{
	    cameraYMax = cameraCtrl.yLimit;
	}
	return cameraXMin != 0f || cameraXMax != 0f || cameraYMin != 0f || cameraYMax != 0f;
    }

    public void SetXMin(float xmin)
    {
	cameraXMin = xmin;
    }

    public void SetXMax(float xmax)
    {
	cameraXMax = xmax;
    }

}

记得都设置成-1:

 

然后来到CameraTarget.cs中:

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

public class CameraTarget : MonoBehaviour
{
    private bool verboseMode = true;
    public bool isGameplayScene;

    private float slowTimer;
    public float slowTime = 0.5f;
    private float dampTimeX;
    private float dampTimeY;
    public float dampTimeSlow;
    public float dampTimeNormal;

    private float snapDistance = 0.15f;

    private Vector3 heroPrevPosition;
    public Vector3 destination;
    public Vector3 velocityX;
    public Vector3 velocityY;

    public float xOffset; //相机X轴方向上的偏移量
    public float xLookAhead; //相机X轴方向上的提前量

    public float dashOffset; //冲刺时的相机偏移量
    public float fallOffset; //下落时相机的偏移量
    public float dashLookAhead;//冲刺时相机的提前量

    public bool enteredLeft; //从哪个方向进入锁定区域的
    public bool enteredRight;
    public bool enteredTop;
    public bool enteredBot;
    public bool enteredFromLockZone;

    public float xLockMin;
    public float xLockMax;
    public float yLockMin;
    public float yLockMax;

    public bool exitedLeft;//从哪个方向离开锁定区域的
    public bool exitedRight;
    public bool exitedTop;
    public bool exitedBot;

    [HideInInspector]
    public GameManager gm;
    [HideInInspector]
    public HeroController hero_ctrl;
    private Transform heroTransform;

    public CameraController cameraCtrl;
    public TargetMode mode;
    public bool stickToHeroX;//是否黏住玩家的X位置
    public bool stickToHeroY;//是否黏住玩家的Y位置

    public bool fallStick;//是否黏住玩家下落的位置
    public float fallCatcher; //下落位置捕捉

    private void Awake()
    {
	GameInit();
	SceneInit();
    }

    public void GameInit()
    {
	gm = GameManager.instance;
	if(cameraCtrl == null)
	{
	    cameraCtrl = GetComponentInParent<CameraController>();
	}
    }

    public void SceneInit()
    {
	if(gm == null)
	{
	    gm = GameManager.instance;
	}
	isGameplayScene = true;
	hero_ctrl = HeroController.instance;
	heroTransform = hero_ctrl.transform;
	mode = TargetMode.FOLLOW_HERO;
	stickToHeroX = true;
	stickToHeroY = true;
	fallCatcher = 0f;
	xLockMin = 0f;
	xLockMax = cameraCtrl.xLimit;
	yLockMin = 0f;
	yLockMax = cameraCtrl.yLimit;
    }

    private void Update()
    {
	if(hero_ctrl == null || !isGameplayScene)
	{
	    mode = TargetMode.FREE;
	    return;
	}
	if (isGameplayScene)
	{
	    float num = transform.position.x;
	    float num2 = transform.position.y;
	    float z = transform.position.z;
	    float x = heroTransform.position.x;
	    float y = heroTransform.position.y;
	    Vector3 position = heroTransform.position;
	    if(mode == TargetMode.FOLLOW_HERO)
	    {
		SetDampTime();
		destination = heroTransform.position;
		if (!fallStick && fallCatcher <= 0f)
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, transform.position.y, z), ref velocityX, dampTimeX).x, Vector3.SmoothDamp(transform.position, new Vector3(transform.position.x, destination.y, z), ref velocityY, dampTimeY).y, z);
		}
		else
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, transform.position.y, z), ref velocityX, dampTimeX).x, transform.position.y, z);
		}
		num = transform.position.x;
		num2 = transform.position.y;
		z = transform.position.z;
		if ((heroPrevPosition.x < num && x > num) || (heroPrevPosition.x > num && x < num) || (num >= x - snapDistance && num <= x + snapDistance))
		{
		    stickToHeroX = true;
		}
		if ((heroPrevPosition.y < num2 && y > num2) || (heroPrevPosition.y > num2 && y < num2) || (num2 >= y - snapDistance && num2 <= y + snapDistance))
		{
		    stickToHeroY = true;
		}
		if (stickToHeroX)
		{
		    transform.SetPositionX(x);
		    num = x;
		}
		if (stickToHeroY)
		{
		    transform.SetPositionY(y);
		    num2 = y;
		}
	    }
	    if(mode == TargetMode.LOCK_ZONE)
	    {
		SetDampTime();
		destination = heroTransform.position;
		if(destination.x < xLockMin)
		{
		    destination.x = xLockMin;
		}
		if (destination.x > xLockMax)
		{
		    destination.x = xLockMax;
		}
		if (destination.y < yLockMin)
		{
		    destination.y = yLockMin;
		}
		if (destination.y > yLockMax)
		{
		    destination.y = yLockMax;
		}
		if (!fallStick && fallCatcher <= 0f)
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, num2, z), ref velocityX, dampTimeX).x, Vector3.SmoothDamp(transform.position, new Vector3(num, destination.y, z), ref velocityY, dampTimeY).y, z);
		}
		else
		{
		    transform.position = new Vector3(Vector3.SmoothDamp(transform.position, new Vector3(destination.x, num2, z), ref velocityX, dampTimeX).x, num2, z);
		}
		num = transform.position.x;
		num2 = transform.position.y;
		z = transform.position.z;
		if ((heroPrevPosition.x < num && x > num) || (heroPrevPosition.x > num && x < num) || (num >= x - snapDistance && num <= x + snapDistance))
		{
		    stickToHeroX = true;
		}
		if ((heroPrevPosition.y < num2 && y > num2) || (heroPrevPosition.y > num2 && y < num2) || (num2 >= y - snapDistance && num2 <= y + snapDistance))
		{
		    stickToHeroY = true;
		}
		if (stickToHeroX)
		{
		    bool flag = false;
		    if (x >= xLockMin && x <= xLockMax)
		    {
			flag = true;
		    }
		    if (x <= xLockMax && x >= num)
		    {
			flag = true;
		    }
		    if (x >= xLockMin && x <= num)
		    {
			flag = true;
		    }
		    if (flag)
		    {
			transform.SetPositionX(x);
			num = x;
		    }
		}
		if (stickToHeroY)
		{
		    bool flag2 = false;
		    if (y >= yLockMin && y <= yLockMax)
		    {
			flag2 = true;
		    }
		    if (y <= yLockMax && y >= num2)
		    {
			flag2 = true;
		    }
		    if (y >= yLockMin && y <= num2)
		    {
			flag2 = true;
		    }
		    if (flag2)
		    {
			transform.SetPositionY(y);
		    }
		}
	    }
	    if(hero_ctrl != null)
	    {
		if (hero_ctrl.cState.facingRight)
		{
		    if(xOffset < xLookAhead)
		    {
			xOffset += Time.deltaTime * 6f;
		    }
		}
		else if (xOffset > -xLookAhead)
		{
		    xOffset -= Time.deltaTime * 6f;
		}
		if(xOffset < -xLookAhead)
		{
		    xOffset = -xLookAhead;
		}
		if (xOffset > xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if(mode == TargetMode.LOCK_ZONE)
		{
		    if (x < xLockMin && hero_ctrl.cState.facingRight)
		    {
			xOffset = x - num + 1f;
		    }
		    if (x > xLockMax && !hero_ctrl.cState.facingRight)
		    {
			xOffset = x - num - 1f;
		    }
		    if (num + xOffset > xLockMax)
		    {
			xOffset = xLockMax - num;
		    }
		    if (num + xOffset < xLockMin)
		    {
			xOffset = xLockMin - num;
		    }
		}
		if (xOffset < -xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if (xOffset > xLookAhead)
		{
		    xOffset = xLookAhead;
		}
		if (hero_ctrl.cState.dashing && (hero_ctrl.current_velocity.x > 5f || hero_ctrl.current_velocity.x < -5f))
		{
		    if (hero_ctrl.cState.facingRight)
		    {
			dashOffset = dashLookAhead;
		    }
		    else
		    {
			dashOffset = -dashLookAhead;
		    }
		    if (mode == TargetMode.LOCK_ZONE)
		    {
			if (num + dashOffset > xLockMax)
			{
			    dashOffset = 0f;
			}
			if (num + dashOffset < xLockMin)
			{
			    dashOffset = 0f;
			}
			if (x > xLockMax || x < xLockMin)
			{
			    dashOffset = 0f;
			}
		    }
		}
		else
		{
		    dashOffset = 0f;
		}
		heroPrevPosition = heroTransform.position;
	    }
	    if(hero_ctrl != null && !hero_ctrl.cState.falling)
	    {
		fallCatcher = 0f;
		fallStick = false;
	    }
	    if (mode == TargetMode.FOLLOW_HERO || mode == TargetMode.LOCK_ZONE)
	    {
		if (hero_ctrl.cState.falling && cameraCtrl.transform.position.y > y + 0.1f && !fallStick && (cameraCtrl.transform.position.y - 0.1f >= yLockMin || mode != TargetMode.LOCK_ZONE))
		{
		    Debug.LogFormat("Fall Catcher");								    
		    cameraCtrl.transform.SetPositionY(cameraCtrl.transform.position.y - fallCatcher * Time.deltaTime);
		    if (mode == TargetMode.LOCK_ZONE && cameraCtrl.transform.position.y < yLockMin)
		    {
			cameraCtrl.transform.SetPositionY(yLockMin);
		    }
		    if (cameraCtrl.transform.position.y < 8.3f)
		    {
			cameraCtrl.transform.SetPositionY(8.3f);
		    }
		    if (fallCatcher < 25f)
		    {
			fallCatcher += 80f * Time.deltaTime;
		    }
		    if (cameraCtrl.transform.position.y < heroTransform.position.y + 0.1f)
		    {
			fallStick = true;
		    }
		    transform.SetPositionY(cameraCtrl.transform.position.y);
		    num2 = cameraCtrl.transform.position.y;
		}
		if (fallStick)
		{
		    fallCatcher = 0f;
		    if (heroTransform.position.y + 0.1f >= yLockMin || mode != TargetMode.LOCK_ZONE)
		    {
			Debug.LogFormat("将cameraCtrl的Y坐标设置成heroTransform,再将cameraTarget的Y坐标设置成cameraCtrl的Y坐标");
			cameraCtrl.transform.SetPositionY(heroTransform.position.y + 0.1f);
			transform.SetPositionY(cameraCtrl.transform.position.y);
			num2 = cameraCtrl.transform.position.y;
		    }
		    if (mode == TargetMode.LOCK_ZONE && cameraCtrl.transform.position.y < yLockMin)
		    {
			cameraCtrl.transform.SetPositionY(yLockMin);
		    }
		    if (cameraCtrl.transform.position.y < 8.3f)
		    {
			cameraCtrl.transform.SetPositionY(8.3f);
		    }
		}
	    }
	}
    }

    public void PositionToStart()
    {
	float x = transform.position.x;
	Vector3 position = transform.position;
	float x2 = heroTransform.position.x;
	float y = heroTransform.position.y;
	velocityX = Vector3.zero;
	velocityY = Vector3.zero;
	destination = heroTransform.position;
	if (hero_ctrl.cState.facingRight)
	{
	    xOffset = 1f;
	}
	else
	{
	    xOffset = -1f;
	}
	if (mode == TargetMode.LOCK_ZONE)
	{
	    if (x2 < xLockMin && hero_ctrl.cState.facingRight)
	    {
		xOffset = x2 - x + 1f;
	    }
	    if (x2 > xLockMax && !hero_ctrl.cState.facingRight)
	    {
		xOffset = x2 - x - 1f;
	    }
	    if (x + xOffset > xLockMax)
	    {
		xOffset = xLockMax - x;
	    }
	    if (x + xOffset < xLockMin)
	    {
		xOffset = xLockMin - x;
	    }
	}
	if (xOffset < -xLookAhead)
	{
	    xOffset = -xLookAhead;
	}
	if (xOffset > xLookAhead)
	{
	    xOffset = xLookAhead;
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CT PTS - xOffset: {0} HeroPos: {1}, {2}", new object[]
	    {
		xOffset,
		x2,
		y
	    });
	}
	if (mode == TargetMode.FOLLOW_HERO)
	{
	    if (verboseMode)
	    {
		Debug.LogFormat("CT PTS - Follow Hero - CT Pos: {0}", new object[]
		{
		    base.transform.position
		});
	    }
	    transform.position = cameraCtrl.KeepWithinSceneBounds(destination);
	}
	else if (mode == TargetMode.LOCK_ZONE)
	{
	    if (destination.x < xLockMin)
	    {
		destination.x = xLockMin;
	    }
	    if (destination.x > xLockMax)
	    {
		destination.x = xLockMax;
	    }
	    if (destination.y < yLockMin)
	    {
		destination.y = yLockMin;
	    }
	    if (destination.y > yLockMax)
	    {
		destination.y = yLockMax;
	    }
	    transform.position = destination;
	    if (verboseMode)
	    {
		Debug.LogFormat("CT PTS - Lock Zone - CT Pos: {0}", new object[]
		{
		    transform.position
		});
	    }
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CT - PTS: HeroPos: {0} Mode: {1} Dest: {2}", new object[]
	    {
		heroTransform.position,
		mode,
		destination
	    });
	}
	heroPrevPosition = heroTransform.position;
    }

    /// <summary>
    /// 进入锁定区域,使用的是dampTimeSlow
    /// </summary>
    /// <param name="xLockMin_var"></param>
    /// <param name="xLockMax_var"></param>
    /// <param name="yLockMin_var"></param>
    /// <param name="yLockMax_var"></param>
    public void EnterLockZone(float xLockMin_var, float xLockMax_var, float yLockMin_var, float yLockMax_var)
    {
	xLockMin = xLockMin_var;
	xLockMax = xLockMax_var;
	yLockMin = yLockMin_var;
	yLockMax = yLockMax_var;
	mode = TargetMode.LOCK_ZONE;
	float x = transform.position.x;
	float y = transform.position.y;
	Vector3 position = transform.position;
	float x2 = heroTransform.position.x;
	float y2 = heroTransform.position.y;
	Vector3 position2 = heroTransform.position;
	if ((!enteredLeft || xLockMin != 14.6f) && (!enteredRight || xLockMax != cameraCtrl.xLimit))
	{
	    dampTimeX = dampTimeSlow;
	}
	if ((!enteredBot || yLockMin != 8.3f) && (!enteredTop || yLockMax != cameraCtrl.yLimit))
	{
	    dampTimeY = dampTimeSlow;
	}
	slowTimer = slowTime;
	if (x >= x2 - snapDistance && x <= x2 + snapDistance)
	{
	    stickToHeroX = true;
	}
	else
	{
	    stickToHeroX = false;
	}
	if (y >= y2 - snapDistance && y <= y2 + snapDistance)
	{
	    stickToHeroY = true; 
	}
	else
	{
	    stickToHeroY = false;
	}
    }

    /// <summary>
    /// 迅速进入锁定区域,
    /// </summary>
    /// <param name="xLockMin_var"></param>
    /// <param name="xLockMax_var"></param>
    /// <param name="yLockMin_var"></param>
    /// <param name="yLockMax_var"></param>
    public void EnterLockZoneInstant(float xLockMin_var, float xLockMax_var, float yLockMin_var, float yLockMax_var)
    {
	xLockMin = xLockMin_var;
	xLockMax = xLockMax_var;
	yLockMin = yLockMin_var;
	yLockMax = yLockMax_var;
	mode = TargetMode.LOCK_ZONE;
	if(transform.position.x < xLockMin)
	{
	    transform.SetPositionX(xLockMin);
	}
	if (transform.position.x > xLockMax)
	{
	    transform.SetPositionX(xLockMax);
	}
	if (transform.position.y < yLockMin)
	{
	    transform.SetPositionY(yLockMin);
	}
	if (transform.position.y > yLockMax)
	{
	    transform.SetPositionY(yLockMax);
	}
	stickToHeroX = true;
	stickToHeroY = true;
    }

    /// <summary>
    /// 离开锁定区域
    /// </summary>
    public void ExitLockZone()
    {
	if (mode == TargetMode.FREE)
	    return;
	if (hero_ctrl.cState.hazardDeath || hero_ctrl.cState.dead )
	{
	    mode = TargetMode.FREE;
	}
	else
	{
	    mode = TargetMode.FOLLOW_HERO;
	}
	if ((!exitedLeft || xLockMin != 14.6f) && (!exitedRight || xLockMax != cameraCtrl.xLimit))
	{
	    dampTimeX = dampTimeSlow;
	}
	if ((!exitedBot || yLockMin != 8.3f) && (!exitedTop || yLockMax != cameraCtrl.yLimit))
	{
	    dampTimeY = dampTimeSlow;
	}
	slowTimer = slowTime;
	stickToHeroX = false;
	stickToHeroY = false;
	fallStick = false;
	xLockMin = 0f;
	xLockMax = cameraCtrl.xLimit;
	yLockMin = 0f;
	yLockMax = cameraCtrl.yLimit;
	if(hero_ctrl!= null)
	{
	    if(transform.position.x >= heroTransform.position.x - snapDistance && transform.position.x <= heroTransform.position.x + snapDistance)
	    {
		stickToHeroX = true;
	    }
	    else
	    {
		stickToHeroX = false;
	    }
	    if (transform.position.y >= heroTransform.position.y - snapDistance && transform.position.y <= heroTransform.position.y + snapDistance)
	    {
		stickToHeroY = true;
	    }
	    else
	    {
		stickToHeroY = false;
	    }
	}
    }

    /// <summary>
    /// 设置Damp时间
    /// </summary>
    private void SetDampTime()
    {
	if (slowTimer > 0f)
	{
	    slowTimer -= Time.deltaTime;
	    return;
	}
	if (dampTimeX > dampTimeNormal)
	{
	    dampTimeX -= 0.007f;
	}
	else if (dampTimeX < dampTimeNormal)
	{
	    dampTimeX = dampTimeNormal;
	}
	if (dampTimeY > dampTimeNormal)
	{
	    dampTimeY -= 0.007f;
	    return;
	}
	if (dampTimeY < dampTimeNormal)
	{
	    dampTimeY = dampTimeNormal;
	}
    }

    public enum TargetMode
    {
	FOLLOW_HERO,
	LOCK_ZONE,
	BOSS,
	FREE
    }
}

来到CameraController.cs中,我们继续补充视角锁定系统:

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

public class CameraController : MonoBehaviour
{
    private bool verboseMode = true;

    public CameraMode mode;
    private CameraMode prevMode;

    public bool atSceneBounds;//是否位于场景的边界外
    public bool atHorizontalSceneBounds;//是否位于场景的X轴方向的边界外

    public tk2dTileMap tilemap; //通过tk2dTileMap的高度和宽度来确定场景的宽度和长度以及摄像机限制
    public float sceneWidth; //场景的高度
    public float sceneHeight; //场景的宽度
    public float xLimit;
    public float yLimit;

    public Vector3 destination;
    private float targetDeltaX; //目标在Time.DeltaTime移动的X方向距离
    private float targetDeltaY;//目标在Time.DeltaTime移动的Y方向距离

    public float lookOffset; //视线偏移量

    private Vector3 velocity;
    private Vector3 velocityX;
    private Vector3 velocityY;
    private float maxVelocityCurrent;
    public float maxVelocity; //可容忍的最大速度

    public float dampTime; //damp时间
    public float dampTimeX;//X方向的damp时间
    public float dampTimeY;//Y方向的damp时间

    private float startLockedTimer; //开始计入锁定区域的倒计时
    public List<CameraLockArea> lockZoneList; //场景中所有的CameraLockArea
    public float xLockMin;
    public float xLockMax;
    public float yLockMin;
    public float yLockMax;
    private CameraLockArea currentLockArea; //当前的锁定区域
    public Vector2 lastLockPosition; 

    private Camera cam;
    public CameraTarget camTarget;
    private GameManager gm;
    private HeroController hero_ctrl;
    private Transform cameraParent;


    private void Awake()
    {
	GameInit();
	SceneInit();
    }

    private void LateUpdate()
    {
	float x = transform.position.x;
	float y = transform.position.y;
	float z = transform.position.z;
	float x2 = cameraParent.position.x;
	float y2 = cameraParent.position.y;
	if(mode != CameraMode.FROZEN)
	{
	    lookOffset = 0f;
	    UpdateTargetDestinationDelta();
	    Vector3 vector = cam.WorldToViewportPoint(camTarget.transform.position);
	    Vector3 vector2 = new Vector3(targetDeltaX, targetDeltaY, 0f) - cam.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, vector.z));
	    destination = new Vector3(x + vector2.x, y + vector2.y, z);
	    if(mode == CameraMode.LOCKED && currentLockArea != null)
	    {
		if(lookOffset > 0f && currentLockArea.preventLookUp && destination.y > currentLockArea.cameraYMax)
		{
		    if (transform.position.y > currentLockArea.cameraYMax)
		    {
			destination = new Vector3(destination.x, destination.y - lookOffset, destination.z);
		    }
		    else
		    {
			destination = new Vector3(destination.x, currentLockArea.cameraYMax, destination.z);
		    }
		}
		if(lookOffset < 0f && currentLockArea.preventLookDown && destination.y < currentLockArea.cameraYMin)
		{
		    if (transform.position.y < currentLockArea.cameraYMin)
		    {
			destination = new Vector3(destination.x, destination.y - lookOffset, destination.z);
		    }
		    else
		    {
			destination = new Vector3(destination.x, currentLockArea.cameraYMin, destination.z);
		    }
		}
	    }
	    if(mode == CameraMode.FOLLOWING || mode == CameraMode.LOCKED)
	    {
		destination = KeepWithinSceneBounds(destination);
	    }
	    Vector3 vector3 = Vector3.SmoothDamp(transform.position, new Vector3(destination.x, y, z), ref velocityX, dampTimeX);
	    Vector3 vector4 = Vector3.SmoothDamp(transform.position, new Vector3(x, destination.y, z), ref velocityY, dampTimeY);
	    transform.SetPosition2D(vector3.x, vector4.y);
	    x = transform.position.x;
	    y = transform.position.y;
	    if(velocity.magnitude > maxVelocityCurrent)
	    {
		velocity = velocity.normalized * maxVelocityCurrent;
	    }
	}
	if (x + x2 < 14.6f)
	{
	    transform.SetPositionX(14.6f);
	}
	if (transform.position.x + x2 > xLimit)
	{
	    transform.SetPositionX(xLimit);
	}
	if (transform.position.y + y2 < 8.3f)
	{
	    transform.SetPositionY(8.3f);
	}
	if (transform.position.y + y2 > yLimit)
	{
	    transform.SetPositionY(yLimit);
	}
	if (startLockedTimer > 0f)
	{
	    startLockedTimer -= Time.deltaTime;
	}
    }

    public void GameInit()
    {
	gm = GameManager.instance;
	cam = GetComponent<Camera>();
	cameraParent = transform.parent.transform;
    }

    public void SceneInit()
    {
	startLockedTimer = 0.5f;
	velocity = Vector3.zero;
	if(hero_ctrl == null)
	{
	    hero_ctrl = HeroController.instance;
	    hero_ctrl.heroInPosition += PositionToHero;
	}
	lockZoneList = new List<CameraLockArea>();
	GetTilemapInfo();
	xLockMin = 0f;
	xLockMax = xLimit;
	yLockMin = 0f;
	yLockMax = yLimit;
	dampTimeX = dampTime;
	dampTimeY = dampTime;
	maxVelocityCurrent = maxVelocity;

    }

    public void PositionToHero(bool forceDirect)
    {
	StartCoroutine(DoPositionToHero(forceDirect));
    }

    /// <summary>
    /// 进入锁定区域
    /// </summary>
    /// <param name="lockArea"></param>
    public void LockToArea(CameraLockArea lockArea)
    {
	if (!lockZoneList.Contains(lockArea))
	{
	    if (verboseMode)
	    {
		Debug.LogFormat("LockZone Activated: {0} at startLockedTimer {1} ({2}s)", new object[]
		{
		    lockArea.name,
		    startLockedTimer,
		    Time.timeSinceLevelLoad
		});
	    }
	    lockZoneList.Add(lockArea);
	    if (currentLockArea != null && currentLockArea.maxPriority && !lockArea.maxPriority)
		return;
	    currentLockArea = lockArea;
	    SetMode(CameraMode.LOCKED);
	    if(lockArea.cameraXMin < 0f)
	    {
		xLockMin = 14.6f;
	    }
	    else
	    {
		xLockMin = lockArea.cameraXMin;
	    }
	    if(lockArea.cameraXMax < 0f)
	    {
		xLockMax = xLimit;
	    }
	    else
	    {
		xLockMax = lockArea.cameraXMax;
	    }
	    if(lockArea.cameraXMin < 0f)
	    {
		yLockMin = 8.3f;
	    }
	    else
	    {
		yLockMin = lockArea.cameraYMin;
	    }
	    if (lockArea.cameraYMax < 0f)
	    {
		yLockMax = yLimit;
	    }
	    else
	    {
		yLockMax = lockArea.cameraYMax;
	    }
	    if(startLockedTimer > 0f) //迅速进入锁定区域
	    {
		Debug.LogFormat("Enter Lock Zone Instant!");
		camTarget.transform.SetPosition2D(KeepWithinSceneBounds(hero_ctrl.transform.position));
		camTarget.destination = camTarget.transform.position;
		camTarget.EnterLockZoneInstant(xLockMin, xLockMax, yLockMin, yLockMax);
		transform.SetPosition2D(KeepWithinSceneBounds(hero_ctrl.transform.position));
		destination = transform.position;
		return;
	    }
	    camTarget.EnterLockZone(xLockMin, xLockMax, yLockMin, yLockMax); //不用这么急的进入锁定区域
	}
    }

    /// <summary>
    /// 释放锁定区域
    /// </summary>
    /// <param name="lockArea"></param>
    public void ReleaseLock(CameraLockArea lockArea)
    {
	lockZoneList.Remove(lockArea);
	if (verboseMode)
	{
	    Debug.Log("LockZone Released " + lockArea.name);
	}
	if (lockArea == currentLockArea)
	{
	    if(lockZoneList.Count > 0)
	    {
		currentLockArea = lockZoneList[lockZoneList.Count - 1];
		xLockMin = currentLockArea.cameraXMin;
		xLockMax = currentLockArea.cameraXMax;
		yLockMin = currentLockArea.cameraYMin;
		yLockMax = currentLockArea.cameraYMax;
		camTarget.enteredFromLockZone = true;
		camTarget.EnterLockZone(xLockMin, xLockMax, yLockMin, yLockMax);
		return;
	    }
	    lastLockPosition = transform.position;
	    if (camTarget != null)
	    {
		camTarget.enteredFromLockZone = false;
		camTarget.ExitLockZone();
	    }
	    currentLockArea = null;
	    if (!hero_ctrl.cState.dead)
	    {
		SetMode(CameraMode.FOLLOWING);
		return;	
	    }
	}
	else if (verboseMode)
	{
	    Debug.Log("LockZone was not the current lock when removed.");
	}
    }

    /// <summary>
    /// 将位置锁定到主角身上
    /// </summary>
    /// <param name="forceDirect"></param>
    /// <returns></returns>
    private IEnumerator DoPositionToHero(bool forceDirect)
    {
	yield return new WaitForFixedUpdate();
	GetTilemapInfo();
	camTarget.PositionToStart();
	CameraMode previousMode = mode;
	SetMode(CameraMode.FROZEN);

	Vector3 newPosition = KeepWithinSceneBounds(camTarget.transform.position);
	if (verboseMode)
	{
	    Debug.LogFormat("CC - STR: NewPosition: {0} TargetDelta: ({1}, {2}) CT-XOffset: {3} HeroPos: {4} CT-Pos: {5}", new object[]
	    {
		newPosition,
		targetDeltaX,
		targetDeltaY,
		camTarget.xOffset,
		hero_ctrl.transform.position,
		camTarget.transform.position
	    });
	}
	if (forceDirect)
	{
	    if (verboseMode)
	    {
		Debug.Log("====> TEST 1a - ForceDirect Positioning Mode");
	    }
	    transform.SetPosition2D(newPosition);
	}
	else
	{
	    bool flag2;
	    bool flag = IsAtHorizontalSceneBounds(newPosition, out flag2);
	    bool flag3 = false;
	    if(currentLockArea != null)
	    {
		flag3 = true;
	    }
	    if (flag3)
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 3 - Lock Zone Active");
		}
		PositionToHeroFacing(newPosition, true);
		transform.SetPosition2D(KeepWithinSceneBounds(transform.position));
	    }
	    else
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 4 - No Lock Zone");
		}
		PositionToHeroFacing(newPosition, false);
	    }
	    if (flag)
	    {
		if (verboseMode)
		{
		    Debug.Log("====> TEST 2 - At Horizontal Scene Bounds");
		}
		if ((flag2 && !hero_ctrl.cState.facingRight) || (!flag2 && hero_ctrl.cState.facingRight))
		{
		    if (verboseMode)
		    {
			Debug.Log("====> TEST 2a - Hero Facing Bounds");
		    }
		    transform.SetPosition2D(newPosition);
		}
		else
		{
		    if (verboseMode)
		    {
			Debug.Log("====> TEST 2b - Hero Facing Inwards");
		    }
		    if (IsTouchingSides(targetDeltaX))
		    {
			if (verboseMode)
			{
			    Debug.Log("Xoffset still touching sides");
			}
			transform.SetPosition2D(newPosition);
		    }
		    else
		    {
			if (verboseMode)
			{
			    Debug.LogFormat("Not Touching Sides with Xoffset CT: {0} Hero: {1}", new object[]
			    {
				camTarget.transform.position,
				hero_ctrl.transform.position
			    });
			}
			if (hero_ctrl.cState.facingRight)
			{
			    transform.SetPosition2D(hero_ctrl.transform.position.x + 1f, newPosition.y);
			}
			else
			{
			    transform.SetPosition2D(hero_ctrl.transform.position.x - 1f, newPosition.y);
			}
		    }
		}
	    }
	}
	destination = transform.position;
	velocity = Vector3.zero;
	velocityX = Vector3.zero;
	velocityY = Vector3.zero;
	yield return new WaitForSeconds(0.1f);

	if(previousMode == CameraMode.FROZEN)
	{
	    SetMode(CameraMode.FOLLOWING);
	}
	else if(previousMode == CameraMode.LOCKED)
	{
	    if (currentLockArea != null)
	    {
		SetMode(previousMode);
	    }
	    else
	    {
		SetMode(CameraMode.FOLLOWING);
	    }
	}
	else
	{
	    SetMode(previousMode);
	}
	if (verboseMode)
	{
	    Debug.LogFormat("CC - PositionToHero FIN: - TargetDelta: ({0}, {1}) Destination: {2} CT-XOffset: {3} NewPosition: {4} CamTargetPos: {5} HeroPos: {6}", new object[]
	    {
		targetDeltaX,
		targetDeltaY,
		destination,
		camTarget.xOffset,
		newPosition,
		camTarget.transform.position,
		hero_ctrl.transform.position
	    });
	}
    }

    private void PositionToHeroFacing(Vector3 newPosition, bool useXOffset)
    {
	if (useXOffset)
	{
	    transform.SetPosition2D(newPosition.x + camTarget.xOffset, newPosition.y);
	    return;
	}
	if (hero_ctrl.cState.facingRight)
	{
	    transform.SetPosition2D(newPosition.x + 1f, newPosition.y);
	    return;
	}
	transform.SetPosition2D(newPosition.x - 1f, newPosition.y);
    }

    /// <summary>
    /// 获取当前场景的TileMap的信息
    /// </summary>
    private void GetTilemapInfo()
    {
	tilemap = gm.tilemap;
	sceneWidth = tilemap.width;
	sceneHeight = tilemap.height;
	xLimit = sceneWidth - 14.6f;
	yLimit = sceneHeight - 8.3f;
    }

    /// <summary>
    /// 更新当前的targetDeltaX和targetDeltaY
    /// </summary>
    private void UpdateTargetDestinationDelta()
    {
	targetDeltaX = camTarget.transform.position.x + camTarget.xOffset + camTarget.dashOffset;
	targetDeltaY = camTarget.transform.position.y + camTarget.fallOffset + lookOffset;
    }

    /// <summary>
    /// 是否位于横向的场景边界
    /// </summary>
    /// <param name="targetDest"></param>
    /// <param name="leftSide"></param>
    /// <returns></returns>
    private bool IsAtHorizontalSceneBounds(Vector2 targetDest, out bool leftSide)
    {
	bool result = false;
	leftSide = false;
	if (targetDest.x <= 14.6f)
	{
	    result = true;
	    leftSide = true;
	}
	if (targetDest.x >= xLimit)
	{
	    result = true;
	    leftSide = false;
	}
	return result;
    }

    /// <summary>
    /// 保持在场景边界内
    /// </summary>
    /// <param name="targetDest"></param>
    /// <returns></returns>
    public Vector3 KeepWithinSceneBounds(Vector3 targetDest)
    {
	Vector3 vector = targetDest;
	bool flag = false;
	bool flag2 = false;
	if (vector.x < 14.6f)
	{
	    vector = new Vector3(14.6f, vector.y, vector.z);
	    flag = true;
	    flag2 = true;
	}
	if (vector.x > xLimit)
	{
	    vector = new Vector3(xLimit, vector.y, vector.z);
	    flag = true;
	    flag2 = true;
	}
	if (vector.y < 8.3f)
	{
	    vector = new Vector3(vector.x, 8.3f, vector.z);
	    flag = true;
	}
	if (vector.y > yLimit)
	{
	    vector = new Vector3(vector.x, yLimit, vector.z);
	    flag = true;
	}
	atSceneBounds = flag;
	atHorizontalSceneBounds = flag2;
	return vector;
    }

    /// <summary>
    /// 是否碰到场景边缘
    /// </summary>
    /// <param name="x"></param>
    /// <returns></returns>
    private bool IsTouchingSides(float x)
    {
	bool result = false;
	if (x <= 14.6f)
	{
	    result = true;
	}
	if (x >= xLimit)
	{
	    result = true;
	}
	return result;
    }

    public void SetMode(CameraMode newMode)
    {
	if (newMode != mode)
	{
	    if (newMode == CameraMode.PREVIOUS)
	    {
		mode = prevMode;
		return;
	    }
	    prevMode = mode;
	    mode = newMode;
	}
    }

    public enum CameraMode
    {
	FROZEN,
	FOLLOWING,
	LOCKED,
	PANNING,
	FADEOUT,
	FADEIN,
	PREVIOUS
    }
}

 同样回到HeroController.cs中,我们需要新建一个事件,当玩家进入指定位置后就调用,然后传送给所有订阅这个事件的物体,在这一期指的是Camera物体:

 public delegate void HeroInPosition(bool forceDirect);
    public event HeroInPosition heroInPosition;

private void Start()
    {
        heroInPosition += delegate(bool forceDirect)
        {
            isHeroInPosition = true;
        };
    
        if(heroInPosition != null)
	    {
            heroInPosition(false);
	    }
    }

总结

首先先设置好Camera脚本的相关系数:

 

最后我们来运行游戏看看效果:

 

 

 下一期我们来完善一下地图的可交互物体吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2176360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ping基本使用详解

在网络中ping是一个十分强大的TCP/IP工具。它的作用主要为&#xff1a; 用来检测网络的连通情况和分析网络速度根据域名得到服务器 IP根据 ping 返回的 TTL 值来判断对方所使用的操作系统及数据包经过路由器数量。我们通常会用它来直接 ping ip 地址&#xff0c;来测试网络的连…

Cisco Secure Firewall Management Center Virtual 7.4.2 - 思科 Firepower 管理中心软件

Cisco Secure Firewall Management Center Virtual 7.4.2 - 思科 Firepower 管理中心软件 Firepower Management Center Software 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-fmc-7/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 实现管理任务…

第十三届蓝桥杯真题Java 斐波那契与7(持续更新)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;蓝桥杯关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 问题描述 斐波那契数列的递推公式为: FnFn−1Fn−2Fn​Fn−1​Fn−2​, 其中 …

喜欢把家里打扫得很干净的人,大多活成了这样,不是迷信!

生活中&#xff0c;我们常常会遇到一些喜欢把家里打扫得干干净净的人。 对于这些人来说&#xff0c;整洁的环境不仅是一种生活习惯&#xff0c;更是一种对生活的态度。 其实&#xff0c;这种生活习惯背后&#xff0c;往往隐藏着他们的命运和未来发展。 以下是喜欢把家里打扫…

c++入门 类和对象(中)

文章目录 1. 类的默认成员函数2. 构造函数3. 析构函数4. 拷贝构造函数5. 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 日期类实现 6. 取地址运算符重载6.1 const成员函数6.2 取地址运算符重载 总结 1. 类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;…

AutoGen框架进行多智能体协作—AI Agentic Design Patterns with AutoGen(一)

1. 多代理对话&#xff1a;单口喜剧 在AutoGen中&#xff0c;Agent是一个可以代表人类意图执行操作的实体&#xff0c;发送消息&#xff0c;接收消息&#xff0c;执行操作&#xff0c;生成回复&#xff0c;并与其他代理交互。AutoGen具有一个名为Conversible Agent的内置代理类…

Ps:打开与置入

在 Adobe Photoshop 中&#xff0c;理解不同的“打开”和“置入”命令及其用途&#xff0c;可以根据不同的需求选择最佳方式来管理和编辑图像文件。 ◆ ◆ ◆ 打开 1、Ps菜单&#xff1a;文件/打开 File/Open 快捷键&#xff1a;Ctrl O 用于直接打开现有的图像文件。 打开的…

httpsok-v1.17.0-SSL证书自动续签

&#x1f525;httpsok-v1.17.0-SSL证书自动续签 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具&#xff0c;基于全新的设计理念&#xff0c;专为 Nginx 、OpenResty 服务器设计。已服务众多中小企业&#xff0c;稳定、安全、可靠。 一行命令&#xff0c;一分钟轻松搞定…

naocs注册中心,配置管理,openfeign在idea中实现模块间的调用,getway的使用

一 naocs注册中心步骤 1 nacos下载安装 解压安装包&#xff0c;直接运行bin目录下的startup.cmd 这里双击运行出现问题的情况下 &#xff08;版本低的naocs&#xff09; 在bin目录下 打开cmd 运行以下命令 startup.cmd -m standalone 访问地址&#xff1a; http://localh…

【Linux】趣味讲解“权限“的那些事(重点讲解文件权限,内含su、sudo、chmod、chown、umask等指令)

文章目录 前言1. Linux下用户的分类1.1 su 指令1.1.1 使用su指令切换到其它的用户上1.1.2 使用su指令切换到root上1.1.3 su指令的总结 1.2 sudo指令(对某条指令进行提权)1.2.1 sudo指令的语法1.2.2 由sudo指令引发的思考问题 2. 什么叫做权限2.2 文件权限2.2.1 文件类型2.2.2 文…

UART驱动学习一(UART硬件介绍)

一、UART硬件介绍 1. 串口的硬件介绍 UART的全称是Universal Asynchronous Receiver and Transmitter&#xff0c;即异步发送和接收。串口在嵌入式中用途非常的广泛&#xff0c;主要的用途有&#xff1a; 打印调试信息&#xff1b;外接各种模块&#xff1a;GPS、蓝牙&#xf…

JavaWeb 12.Tomcat10

希望明天能出太阳 或者如果没有太阳的话 希望我能变得更加阳光一点 —— 24.9.25 一、常见的JavaWeb服务器 Web服务器通常由硬件和软件共同构成 硬件&#xff1a;电脑&#xff0c;提供服务供其他客户电脑访问 软件&#xff1a;电脑上安装的服务器软件&#xff0c;安装后能提…

【鸿蒙HarmonyOS NEXT】数据存储之分布式键值数据库

【鸿蒙HarmonyOS NEXT】数据存储之分布式键值数据库 一、环境说明二、分布式键值数据库介绍三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、分布式键值数据库介绍 KVStore简介&#xff1a; 分布式键值数据库…

手机电脑无缝对接,虫洞软件让多屏协同触手可及

在数字化时代&#xff0c;我们的日常生活和工作越来越依赖于电子设备&#xff0c;尤其是智能手机和电脑。但你是否曾因在手机和电脑之间频繁切换而感到烦恼&#xff1f;现在&#xff0c;有了虫洞软件&#xff0c;这一切都将成为过去式。 虫洞——电脑与手机的桥梁 虫洞软件&a…

Kubernetes整体架构与核心组件

一个 Kubernetes 集群的机器节点有两种角色—— Master 和 Node&#xff0c;都可由一个或多个节点组成&#xff0c;且同一个节点可以既是 Master 也是 Node。其中 Master 节点负责全局决策、资源调度、Node 与 Pod 管理&#xff0c;等等&#xff0c;属于管控节点&#xff1b;No…

【unity进阶知识4】封装unity协程工具,避免 GC(垃圾回收)

文章目录 前言封装协程工具类&#xff0c;避免 GC&#xff08;垃圾回收&#xff09;使用1.使用默认方式使用协程2.使用自定义的 CoroutineTool 工具类来等待不同的时间 完结 前言 在 Unity 中&#xff0c;使用 yield return null 、yield return new WaitForEndOfFrame()等会导…

人物型Agent开发(文心智能体平台创作分享)

开发平台&#xff1a;文心智能体平台AgentBuilder | 想象即现实 目录 一、开发灵感 &#xff08;一&#xff09;打破刻板印象 &#xff08;二&#xff09;以古鉴今&#xff0c;探索人性与情感 二、角色分析与设定 &#xff08;一&#xff09;西门庆特质 &#xff08;二&a…

我的深度学习笔记

传统观念认为&#xff1a;在不考虑算力的情况下&#xff0c;网络越深&#xff0c;其准确率就越高&#xff0c;最直接的方法就是把网络设计的越深越好。 事实上&#xff1a;随着网络的层数不断加深&#xff0c;当达到一定的书目之后&#xff0c;训练精度和测试精度都有下降&…

第十三届蓝桥杯真题Java c组C.纸张尺寸(持续更新)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;蓝桥杯关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 【问题描述】 在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm 841mm&#…

AI运用在营销领域的经典案例及解析

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 在前面一篇文章当中&#xff0c;我给大家介…