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

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

文章目录


前言

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:

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来控制随机播放某个片段的脚本:

cs 复制代码
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;
    }
}
cs 复制代码
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")

cs 复制代码
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我忘记哪个地方漏了一个负号

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")

cs 复制代码
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行为好像也是漏了一个负号:

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摄像机控制器系统

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:

cs 复制代码
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跟随小骑士差点给我晃晕了

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;//冲刺时相机的提前量

    [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:

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:

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中:

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中,我们继续补充视角锁定系统:

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物体:

cs 复制代码
 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脚本的相关系数:

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

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

相关推荐
蔗理苦1 小时前
2024-12-24 NO1. XR Interaction ToolKit 环境配置
unity·quest3·xr toolkit
花生糖@1 小时前
Android XR 应用程序开发 | 从 Unity 6 开发准备到应用程序构建的步骤
android·unity·xr·android xr
向宇it2 小时前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
虾球xz2 小时前
游戏引擎学习第55天
学习·游戏引擎
yngsqq2 小时前
一键打断线(根据相交点打断)——CAD c# 二次开发
windows·microsoft·c#
TENET信条3 小时前
day53 第十一章:图论part04
开发语言·c#·图论
虾球xz4 小时前
游戏引擎学习第58天
学习·游戏引擎
anlog4 小时前
C#在自定义事件里传递数据
开发语言·c#·自定义事件
ue星空5 小时前
虚幻引擎结构之UWorld
游戏引擎·虚幻
ue星空5 小时前
虚幻引擎结构之ULevel
游戏引擎·虚幻