[Unity Demo]从零开始制作空洞骑士Hollow Knight第十六集(上篇):制作更多地图,更多敌人,更多可交互对象

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


前言

         hello大家好久没见,之所以隔了这么久才更新并不是因为我又放弃了这个项目,而是接下来要制作的工作太忙碌了,每次我都花了很长的时间解决完一个部分,然后就没力气打开CSDN写文章就直接睡觉去了,现在终于有时间整理下我这半个月都做了什么内容。

        废话少说,接下来我将继续介绍我做的几个代表性场景,,可能你会说:“怎么标题还和上期一模一样”,那当然是没办法的事情,毕竟一期讲完这么多场景,敌人,可交互对象你没看睡着我都快写睡着了,OK我们接着来制作更多地图,更多敌人,更多可交互对象

        另外,我的Github已经更新了,想要查看最新的内容话请到我的Github主页下载工程吧:

GitHub - ForestDango/Hollow-Knight-Demo: A new Hollow Knight Demo after 2 years!


一、第一个代表性场景

1.制作敌人僵尸跳跳虫更多敌人

我突然找到一个有四个新类型敌人的场景,让我们先来介绍这四少吧,但首先还是先搭建好场景

可能看到这里你还不知道这对应的是游戏里的哪个场景,但只要我加上场景景色一切将豁然开朗

往上走去鹿角站,往右走去打苍蝇之母因此记得添加好对应的TransitionPoint。

我们先来制作最简单的僵尸跳跳虫:做好相对应的tk2dsprite和tk2dspriteAnimator。

 

添加相对应的脚本:

 

 老朋友Attack Range攻击距离:

落地状态下的灰尘粒子系统Dust:

创建一个名字为“Zombie Swipe”的playmakerFSM,然后老规矩贴出事件和变量:

逐个讲状态:

初始化阶段:

准备阶段:逐帧判断攻击距离和可视范围内:

判断玩家位置:

攻击准备阶段:

起飞阶段 

落地阶段:

冷却阶段:

回到空闲阶段:

重置Walker脚本的行为:

        这样一个简单跳跳僵尸虫就完成了。

2.制作敌人阿斯匹德更多可交互对象

        然后是制作敌人阿斯匹德,注意,这个不是折磨玩家的原始阿斯匹德,而是只会喷一个方向的:

首先添加好tk2dsprite和tk2dSpriteAnimator:

长开火和短开火的动画:

添加好相应的脚本:

除此之外,我们还要制作攻击用的子弹

我们先来制作玩家离开攻击距离的playmakerFSM:

这里会设置Spitter的playmakerFSM里面的bool变量unalert Range        以及设置bool变量Can See Hero

 

 

回到阿斯匹德spitter中,我们首先创建一个之前讲过的playmakerFSM叫flyer_receive_direction_msg。这个我之前讲蚊子那期用过,就是接收不同方向信息的,

然后才是我们的重点“spitter” FSM:

初始化状态:播放音效和动画,确认初始时的朝向,空闲状态漫无目的的IdleBuzz,通过Alert Range New来判断是否在攻击距离里,并check Can See Hero.

如果满足上述两个条件直接进入Alert状态: 

 

径直向玩家飞去:

判断位置并射线检测:

 

再飞行一小段距离,朝向玩家:

准备开火阶段:

设置发射的子弹朝向玩家,设置好速度

当玩家离开攻击距离后,回到Unalert Frame状态,下一帧再回到Idle状态

 这里我们来创建子弹预制体:

新建一个脚本名字叫EnemyBullet.cs
 

using System.Collections;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class EnemyBullet : MonoBehaviour
{
    public float scaleMin = 1.15f;
    public float scaleMax = 1.45f;
    private float scale;
    [Space]
    public float stretchFactor = 1.2f;
    public float stretchMinX = 0.75f;
    public float stretchMaxY = 1.75f;
    [Space]
    public AudioSource audioSourcePrefab;
    public AudioEvent impactSound;
    private bool active;

    private Rigidbody2D body;
    private tk2dSpriteAnimator anim;
    private Collider2D col;

    private void Awake()
    {
	body = GetComponent<Rigidbody2D>();
	anim = GetComponent<tk2dSpriteAnimator>();
	col = GetComponent<Collider2D>();
    }

    private void OnEnable()
    {
	active = true;
	scale = Random.Range(scaleMin, scaleMax);
	col.enabled = true;
	body.isKinematic = false;
	body.velocity = Vector2.zero;
	body.angularVelocity = 0f;
	anim.Play("Idle");
    }

    private void Update()
    {
	if (active)
	{
	    float rotation = Random.Range(body.velocity.y,body.velocity.x) * 57.295776f;
	    transform.SetRotation2D(rotation);
	    float num = 1f - body.velocity.magnitude * stretchFactor * 0.01f;
	    float num2 = 1f + body.velocity.magnitude * stretchFactor * 0.01f;
	    if (num2 < stretchMinX)
	    {
		num2 = stretchMinX;
	    }
	    if (num > stretchMaxY)
	    {
		num = stretchMaxY;
	    }
	    num *= scale;
	    num2 *= scale;
	    transform.localScale = new Vector3(num2, num, transform.localScale.z);
	}
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
	if (active)
	{
	    active = false;
	    StartCoroutine(Collision(collision.GetSafeContact().Normal, true));
	}
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
	if(active && collision.tag == "HeroBox")
	{
	    active = false;
	    StartCoroutine(Collision(Vector2.zero, false));
	}
    }

    public void OrbitShieldHit(Transform shield)
    {
	if (active)
	{
	    active = false;
	    Vector2 normal = transform.position - shield.position;
	    normal.Normalize();
	    StartCoroutine(Collision(normal, true));
	}
    }

    private IEnumerator Collision(Vector2 normal, bool doRotation)
    {
	transform.localScale = new Vector3(scale, scale, transform.localScale.z);
	body.isKinematic = true;
	body.velocity = Vector2.zero;
	body.angularVelocity = 0f;
	tk2dSpriteAnimationClip impactAnim = anim.GetClipByName("Impact");
	anim.Play(impactAnim);
	if (!doRotation || (normal.y >= 0.75f && Mathf.Abs(normal.x) < 0.5f))
	{
	    transform.SetRotation2D(0f);
	}
	else if (normal.y <= 0.75f && Mathf.Abs(normal.x) < 0.5f)
	{
	    transform.SetRotation2D(180f);
	}
	else if (normal.x >= 0.75f && Mathf.Abs(normal.y) < 0.5f)
	{
	    transform.SetRotation2D(270f);
	}
	else if (normal.x <= 0.75f && Mathf.Abs(normal.y) < 0.5f)
	{
	    transform.SetRotation2D(90f);
	}
	impactSound.SpawnAndPlayOneShot(audioSourcePrefab, transform.position);
	yield return null;
	col.enabled = false;
	yield return new WaitForSeconds((impactAnim.frames.Length - 1) / impactAnim.fps);
	Destroy(gameObject);//TODO:
    }

}

回到Unity编辑器添加好对应的参数,

最后再添加一个子对象,一个小小的亮灯:

3.制作敌人孵化虫和它的孩子

这个敌人我忘了叫什么名字了,暂且叫它孵化虫吧,先添加上tk2dsprite和tk2dspriteanimator:

它的攻击方式只有一种,那就是生下小虫子,然后小虫子来攻击玩家,子对象只需要一个Alert Range New

上面的playmakerFSM除了之前讲过的flyer_receive_direction_msg,还有就是“Hatcher”

这里给它最多生五个孩子

在开始前,我们可以在场景中偏离核心区域的地方预生成它的孩子,并把它们关在笼子里面别到处乱跑,

注意添加好tag,这里我先预生成了15只虫子宝宝并让它们待在这个隐形笼子里面。 

 

如上面阿斯匹德一样Idle状态:

 

 

 检查是否已经生够了到达最大可生成数量:

 来点生下来的音效,设置好生下来的位置,为宝宝的playmakerFSM发送事件SPAWN:

 

 这里有几个自定义playmakerFSM行为脚本我好像忘记说了:

using System;
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{

    [ActionCategory(ActionCategory.Audio)]
    [Tooltip("Instantiate an Audio Player object and play a oneshot sound via its Audio Source.")]
    public class AudioPlayerOneShot : FsmStateAction
    {

	[RequiredField]
	[CheckForComponent(typeof(AudioSource))]
	[Tooltip("The object to spawn. Select Audio Player prefab.")]
	public FsmGameObject audioPlayer;

	[RequiredField]
	[Tooltip("Object to use as the spawn point of Audio Player")]
	public FsmGameObject spawnPoint;

	[CompoundArray("Audio Clips", "Audio Clip", "Weight")]
	public AudioClip[] audioClips;

	[HasFloatSlider(0f, 1f)]
	public FsmFloat[] weights;
	public FsmFloat pitchMin;
	public FsmFloat pitchMax;
	public FsmFloat volume;
	public FsmFloat delay;
	public FsmGameObject storePlayer;

	private AudioSource audio;

	private float timer;

	public override void Reset()
	{
	    spawnPoint = null;
	    audioClips = new AudioClip[3];
	    weights = new FsmFloat[]
	    {
		1f,
		1f,
		1f
	    };
	    pitchMin = 1f;
	    pitchMax = 1f;
	    volume = 1f;
	    timer = 0f;
	}

	public override void OnEnter()
	{
	    timer = 0f;
	    if(delay.Value == 0f)
	    {
		DoPlayRandomClip();
		Finish();
	    }
	}

	public override void OnUpdate()
	{
	    if(delay.Value > 0f)
	    {
		if(timer < delay.Value)
		{
		    timer += Time.deltaTime;
		    return;
		}
		DoPlayRandomClip();
		Finish();
	    }	
	}

	private void DoPlayRandomClip()
	{
	    if (audioClips.Length == 0)
		return;
	    GameObject value = audioPlayer.Value;
	    Vector3 position = spawnPoint.Value.transform.position;
	    Vector3 up = Vector3.up;
	    //TODO:这行记得要改,因为我还没做对象池
	    GameObject gameObject = UnityEngine.Object.Instantiate(audioPlayer.Value, position, Quaternion.Euler(up));
	    audio = gameObject.GetComponent<AudioSource>();
	    int randomWeightIndex = ActionHelpers.GetRandomWeightedIndex(weights);
	    if(randomWeightIndex != -1)
	    {
		AudioClip audioClip = audioClips[randomWeightIndex];
		if(audioClip != null)
		{
		    float pitch = UnityEngine.Random.Range(pitchMin.Value, pitchMax.Value);
		    audio.pitch = pitch;
		    audio.PlayOneShot(audioClip);
		}
	    }
	    audio.volume = volume.Value;
	}

	public AudioPlayerOneShot()
	{
	    pitchMin = 1f;
	    pitchMax = 2f;
	}
    }

}
using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory(ActionCategory.GameObject)]
    [Tooltip("Spawns a random amount of chosen GameObject from global pool and fires them off in random directions.")]
    public class FlingObjectsFromGlobalPool : RigidBody2dActionBase
    {
	[RequiredField]
	[Tooltip("GameObject to spawn.")]
	public FsmGameObject gameObject;
	[Tooltip("GameObject to spawn at (optional).")]
	public FsmGameObject spawnPoint;
	[Tooltip("Position. If a Spawn Point is defined, this is used as a local offset from the Spawn Point position.")]
	public FsmVector3 position;
	[Tooltip("Minimum amount of objects to be spawned.")]
	public FsmInt spawnMin;
	[Tooltip("Maximum amount of objects to be spawned.")]
	public FsmInt spawnMax;
	[Tooltip("Minimum speed objects are fired at.")]
	public FsmFloat speedMin;
	[Tooltip("Maximum speed objects are fired at.")]
	public FsmFloat speedMax;
	[Tooltip("Minimum angle objects are fired at.")]
	public FsmFloat angleMin;
	[Tooltip("Maximum angle objects are fired at.")]
	public FsmFloat angleMax;
	[Tooltip("Randomises spawn points of objects within this range. Leave as 0 and all objects will spawn at same point.")]
	public FsmFloat originVariationX;
	public FsmFloat originVariationY;
	[Tooltip("Optional: Name of FSM on object you want to send an event to after spawn")]
	public FsmString FSM;
	[Tooltip("Optional: Event you want to send to object after spawn")]
	public FsmString FSMEvent;
	private float vectorX;
	private float vectorY;
	private bool originAdjusted;

	public override void Reset()
	{
	    gameObject = null;
	    spawnPoint = null;
	    position = new FsmVector3
	    {
		UseVariable = true
	    };
	    spawnMin = null;
	    spawnMax = null;
	    speedMin = null;
	    speedMax = null;
	    angleMin = null;
	    angleMax = null;
	    originVariationX = null;
	    originVariationY = null;
	    FSM = new FsmString
	    {
		UseVariable = true
	    };
	    FSMEvent = new FsmString
	    {
		UseVariable = true
	    };
	}

	public override void OnEnter()
	{
	    if (gameObject.Value != null)
	    {
		Vector3 a = Vector3.zero;
		Vector3 zero = Vector3.zero;
		if (spawnPoint.Value != null)
		{
		    a = spawnPoint.Value.transform.position;
		    if (!position.IsNone)
		    {
			a += position.Value;
		    }
		}
		else if (!position.IsNone)
		{
		    a = position.Value;
		}
		int num = Random.Range(spawnMin.Value, spawnMax.Value + 1);
		for (int i = 1; i <= num; i++)
		{
		    //TODO:以后创造完对象池后记得替换掉
		    GameObject gameObject = GameObject.Instantiate(this.gameObject.Value, a, Quaternion.Euler(zero));
		    float x = gameObject.transform.position.x;
		    float y = gameObject.transform.position.y;
		    float z = gameObject.transform.position.z;
		    if (originVariationX != null)
		    {
			x = gameObject.transform.position.x + Random.Range(-originVariationX.Value, originVariationX.Value);
			originAdjusted = true;
		    }
		    if (originVariationY != null)
		    {
			y = gameObject.transform.position.y + Random.Range(-originVariationY.Value, originVariationY.Value);
			originAdjusted = true;
		    }
		    if (originAdjusted)
		    {
			gameObject.transform.position = new Vector3(x, y, z);
		    }
		    base.CacheRigidBody2d(gameObject);
		    float num2 = Random.Range(speedMin.Value, speedMax.Value);
		    float num3 = Random.Range(angleMin.Value, angleMax.Value);
		    vectorX = num2 * Mathf.Cos(num3 * 0.017453292f);
		    vectorY = num2 * Mathf.Sin(num3 * 0.017453292f);
		    Vector2 velocity;
		    velocity.x = vectorX;
		    velocity.y = vectorY;
		    rb2d.velocity = velocity;
		    if (!FSM.IsNone)
		    {
			FSMUtility.LocateFSM(gameObject, FSM.Value).SendEvent(FSMEvent.Value);
		    }
		}
	    }
	    Finish();
	}


    }

}

二、第二个代表性场景

1.制作敌人沃姆Worm

说起来你看到这名字可能想不起来这是啥怪物,其实就是遗忘十字路中恶心玩家的那个路障

首先是灰尘粒子系统:

这个石头也是粒子系统:

这个也是粒子系统石头,区别在于,上面那个是Idle状态下播放的粒子系统,而这个是在Burst状态下播放的:

 

这个虫子属于是既能伤害玩家又能伤害敌人的,因此添加上DamageHero和DamageEnemy两个脚本: 

这里我们把将骨钉攻击那期的Nail Slash里面的playmakerfsm“damages_enemy”也给它安排上:

还有自己的Worm Control:

 向上中:

缩回去: 

 已经缩回地里:

是否开启burst rocks的particlesystem:

Burst的时候就在这里开启伤害和Collider: 

自定义行为脚本如下:

using UnityEngine;

namespace HutongGames.PlayMaker.Actions
{
    [ActionCategory("Particle System")]
    [Tooltip("Set particle emission on or off on an object with a particle emitter")]
    public class SetParticleEmission : FsmStateAction
    {
	[RequiredField]
	[Tooltip("The particle emitting GameObject")]
	public FsmOwnerDefault gameObject;
	public FsmBool emission;

	public override void Reset()
	{
	    gameObject = null;
	    emission = false;
	}

	public override void OnEnter()
	{
	    if (gameObject != null)
	    {
		GameObject ownerDefaultTarget = Fsm.GetOwnerDefaultTarget(gameObject);
		if (ownerDefaultTarget != null)
		{
		    ownerDefaultTarget.GetComponent<ParticleSystem>().enableEmission = emission.Value;
		}
	    }
	    Finish();
	}
    }
}
using HutongGames.PlayMaker;
using UnityEngine;

[ActionCategory("Hollow Knight")]
public class SetDamageHeroAmount : FsmStateAction
{
    [UIHint(UIHint.Variable)]
    public FsmOwnerDefault target;
    public FsmInt damageDealt;

    public override void Reset()
    {
	target = new FsmOwnerDefault();
	damageDealt = null;
    }

    public override void OnEnter()
    {
	GameObject safe = target.GetSafe(this);
	if(safe != null)
	{
	    DamageHero component = safe.GetComponent<DamageHero>();
	    if(component != null && !damageDealt.IsNone)
	    {
		component.damageDealt = damageDealt.Value;
	    }
	}
	base.Finish();
    }

}

至此我们制作了一个流动的循环。 

2.制作可交互对象

其实主要就是这些杆子,我们用脚本breakable.cs来制作

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

public class Breakable : MonoBehaviour,IHitResponder
{
    private Collider2D bodyCollider;

    [Tooltip("Renderer which presents the undestroyed object.")]
    [SerializeField] private Renderer wholeRenderer;

    [Tooltip("List of child game objects which also represent the whole object.")]
    [SerializeField] public  GameObject[] wholeParts;

    [Tooltip("List of child game objects which represent remnants that remain static after destruction.")]
    [SerializeField] private GameObject[] remnantParts;

    [SerializeField] private List<GameObject> debrisParts;

    [SerializeField] private float angleOffset = -60f;

    [Tooltip("Breakables behind this threshold are inert.")]
    [SerializeField] private float inertBackgroundThreshold;

    [Tooltip("Breakables in front of this threshold are inert.")]
    [SerializeField] private float inertForegroundThreshold;

    [Tooltip("Breakable effects are spawned at this offset.")]
    [SerializeField] private Vector3 effectOffset;

    [Tooltip("Prefab to spawn for audio.")]
    [SerializeField] private AudioSource audioSourcePrefab;

    [Tooltip("Table of audio clips to play upon break.")]
    [SerializeField] private AudioEvent breakAudioEvent;

    [Tooltip("Table of audio clips to play upon break.")]
    [SerializeField] private RandomAudioClipTable breakAudioClipTable;

    [Tooltip("Prefab to spawn when hit from a non-down angle.")]
    [SerializeField] private Transform dustHitRegularPrefab;

    [Tooltip("Prefab to spawn when hit from a down angle.")]
    [SerializeField] private Transform dustHitDownPrefab;

    [Tooltip("Prefab to spawn when hit from a down angle.")]
    [SerializeField] private float flingSpeedMin;
    [Tooltip("Prefab to spawn when hit from a down angle.")]
    [SerializeField] private float flingSpeedMax;
    [Tooltip("Strike effect prefab to spawn.")]
    [SerializeField] private Transform strikeEffectPrefab;

    [Tooltip("Nail hit prefab to spawn.")]
    [SerializeField] private Transform nailHitEffectPrefab;

    [Tooltip("Spell hit effect prefab to spawn.")]
    [SerializeField] private Transform spellHitEffectPrefab;

    [Tooltip("Object to send HIT event to.")]
    [SerializeField] private GameObject hitEventReciever;

    [Tooltip("Forward break effect to sibling FSMs.")]
    [SerializeField] private bool forwardBreakEvent;

    [Space]
    public Probability.ProbabilityGameObject[] containingParticles;
    public FlingObject[] flingObjectRegister;

    private bool isBroken;

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

    protected void Reset()
    {
	inertBackgroundThreshold = 1f;
	inertForegroundThreshold = -1f;
	effectOffset = new Vector3(0f, 0.5f, 0f);
	flingSpeedMin = 10f;
	flingSpeedMax = 17f;
    }

    protected void Start()
    {
	CreateAdditionalDebrisParts(debrisParts);
	float z = transform.position.z;
	if(z > inertBackgroundThreshold || z < inertForegroundThreshold)
	{
	    BoxCollider2D component = GetComponent<BoxCollider2D>();
	    if(component != null)
	    {
		component.enabled = false;
	    }
	    Destroy(this);
	    return;
	}
	for (int i = 0; i < remnantParts.Length; i++)
	{
	    GameObject gameObject = remnantParts[i];
	    if(gameObject != null && gameObject.activeSelf)
	    {
		gameObject.SetActive(false);
	    }
	}
	angleOffset *= Mathf.Sign(transform.localScale.x);
    }

    protected virtual void CreateAdditionalDebrisParts(List<GameObject> debrisParts)
    {
    }

    public void Hit(HitInstance damageInstance)
    {
	if (isBroken)
	{
	    return;
	}
	Debug.LogFormat("Breakable Take Hit");
	float impactAngle = damageInstance.Direction;
	float num = damageInstance.MagnitudeMultiplier;
	if(damageInstance.AttackType == AttackTypes.Spell)
	{
	    Instantiate(spellHitEffectPrefab, base.transform.position, Quaternion.identity).SetPositionZ(0.0031f);
	}
	else
	{
	    if (damageInstance.AttackType != AttackTypes.Nail && damageInstance.AttackType != AttackTypes.Generic)
	    {
		impactAngle = 90f;
		num = 1f;
	    }
	    Instantiate(strikeEffectPrefab, base.transform.position,Quaternion.identity);
	    Vector3 position = (damageInstance.Source.transform.position + base.transform.position) * 0.5f;
	    SpawnNailHitEffect(nailHitEffectPrefab, position, impactAngle);
	}
	int cardinalDirection = DirectionUtils.GetCardinalDirection(damageInstance.Direction);
	Transform transform = dustHitRegularPrefab;
	float flingAngleMin;
	float flingAngleMax;
	Vector3 euler;
	if (cardinalDirection == 2)
	{
	    angleOffset *= -1f;
	    flingAngleMin = 120f;
	    flingAngleMax = 160f;
	    euler = new Vector3(180f, 90f, 270f);
	}
	else if (cardinalDirection == 0)
	{
	    flingAngleMin = 30f;
	    flingAngleMax = 70f;
	    euler = new Vector3(0f, 90f, 270f);
	}
	else if (cardinalDirection == 1)
	{
	    angleOffset = 0f;
	    flingAngleMin = 70f;
	    flingAngleMax = 110f;
	    num *= 1.5f;
	    euler = new Vector3(270f, 90f, 270f);
	}
	else
	{
	    angleOffset = 0f;
	    flingAngleMin = 160f;
	    flingAngleMax = 380f;
	    transform = dustHitDownPrefab;
	    euler = new Vector3(-72.5f, -180f, -180f);
	}
	if(transform != null)
	{
	    Instantiate(transform, transform.position + effectOffset, Quaternion.Euler(euler));
	}
	Break(flingAngleMin, flingAngleMax, num);
    }

    private static Transform SpawnNailHitEffect(Transform nailHitEffectPrefab, Vector3 position, float impactAngle)
    {
	if (nailHitEffectPrefab == null)
	    return null;
	int cardinalDirection = DirectionUtils.GetCardinalDirection(impactAngle);
	float y = 1.5f;
	float minInclusive;
	float maxInclusive;
	if (cardinalDirection == 3)
	{
	    minInclusive = 270f;
	    maxInclusive = 290f;
	}
	else if (cardinalDirection == 1)
	{
	    minInclusive = 70f;
	    maxInclusive = 110f;
	}
	else
	{
	    minInclusive = 340f;
	    maxInclusive = 380f;
	}
	float x = (cardinalDirection == 2) ? -1.5f : 1.5f;
	Transform transform = Instantiate(nailHitEffectPrefab,position,Quaternion.identity);
	Vector3 eulerAngles = transform.eulerAngles;
	eulerAngles.z = Random.Range(minInclusive, maxInclusive);
	transform.eulerAngles = eulerAngles;
	Vector3 localScale = transform.localScale;
	localScale.x = x;
	localScale.y = y;
	transform.localScale = localScale;
	return transform;
    }

    public void Break(float flingAngleMin, float flingAngleMax, float impactMultiplier)
    {
	if (isBroken)
	    return;
	SetStaticPartsActivation(true);
	for (int i = 0; i < debrisParts.Count; i++)
	{
	    GameObject gameObject = debrisParts[i];
	    if (gameObject == null)
	    {
		Debug.LogErrorFormat(this, "Unassigned debris part in {0}", new object[]
		{
		    this
		});
	    }
	    else
	    {
		gameObject.SetActive(true);
		gameObject.transform.SetRotationZ(gameObject.transform.localEulerAngles.z + angleOffset);
		Rigidbody2D component = gameObject.GetComponent<Rigidbody2D>();
		if (component != null)
		{
		    float num = Random.Range(flingAngleMin, flingAngleMax);
		    Vector2 a = new Vector2(Mathf.Cos(num * 0.017453292f), Mathf.Sin(num * 0.017453292f));
		    float d = Random.Range(flingSpeedMin, flingSpeedMax) * impactMultiplier;
		    component.velocity = a * d;
		}
	    }
	}
	if (containingParticles.Length != 0)
	{
	    GameObject gameObject2 = Probability.GetRandomGameObjectByProbability(containingParticles);
	    if (gameObject2)
	    {
		if (gameObject2.transform.parent != transform)
		{
		    FlingObject flingObject = null;
		    foreach (FlingObject flingObject2 in flingObjectRegister)
		    {
			if (flingObject2.referenceObject == gameObject2)
			{
			    flingObject = flingObject2;
			    break;
			}
		    }
		    if (flingObject != null)
		    {
			flingObject.Fling(transform.position);
		    }
		    else
		    {
			gameObject2 = Instantiate(gameObject2, transform.position, Quaternion.identity);
		    }
		}
		gameObject2.SetActive(true);
	    }
	}
	breakAudioEvent.SpawnAndPlayOneShot(audioSourcePrefab, transform.position);
	breakAudioClipTable.SpawnAndPlayOneShot(audioSourcePrefab, transform.position);
	if (hitEventReciever != null)
	{
	    FSMUtility.SendEventToGameObject(hitEventReciever, "HIT", false);
	}
	if (forwardBreakEvent)
	{
	    FSMUtility.SendEventToGameObject(gameObject, "BREAK", false);
	}
	GameObject gameObject3 = GameObject.FindGameObjectWithTag("CameraParent");
	if (gameObject3 != null)
	{
	    PlayMakerFSM playMakerFSM = PlayMakerFSM.FindFsmOnGameObject(gameObject3, "CameraShake");
	    if(playMakerFSM != null)
	    {
		playMakerFSM.SendEvent("EnemyKillShake");
	    }
	}
	wholeRenderer.enabled = false;
	bodyCollider.enabled = false;
	isBroken = true;
    }

    private void SetStaticPartsActivation(bool v)
    {
	
    }

    [System.Serializable]
    public class FlingObject
    {
	public GameObject referenceObject;
	[Space]
	public int spawnMin;
	public int spawnMax;
	public float speedMin;
	public float speedMax;
	public float angleMin;
	public float angleMax;
	public Vector2 originVariation;

	public FlingObject()
	{
	    spawnMin = 25;
	    spawnMax = 35;
	    speedMin = 9f;
	    speedMax = 20f;
	    angleMin = 20f;
	    angleMax = 160f;
	    originVariation = new Vector2(0.5f, 0.5f);
	}

	public void Fling(Vector3 origin)
	{
	    if (!referenceObject)
	    {
		return;
	    }
	    int num = Random.Range(spawnMin, spawnMax + 1);
	    for (int i = 0; i < num; i++)
	    {
		//TODO:Object Pool
		GameObject gameObject = Instantiate(referenceObject);
		if (gameObject)
		{
		    gameObject.transform.position = origin + new Vector3(Random.Range(-originVariation.x, originVariation.x), Random.Range(-originVariation.y, originVariation.y), 0f);
		    float num2 = Random.Range(speedMin, speedMax);
		    float num3 = Random.Range(angleMin, angleMax);
		    float x = num2 * Mathf.Cos(num3 * 0.017453292f);
		    float y = num2 * Mathf.Sin(num3 * 0.017453292f);
		    Vector2 force = new Vector2(x, y);
		    Rigidbody2D component = gameObject.GetComponent<Rigidbody2D>();
		    if (component)
		    {
			component.AddForce(force, ForceMode2D.Impulse);
		    }
		}
	    }
	}

    }
}

 其三个子对象,一个是杆的顶部top,一个是杆的底部base,最后一个是破坏后蹦出来的石头particlesystem:

制作好一个后,我们就可以照此制作更多的这种可破坏的杆子Pole了,

还有这种可破坏的雕塑,原理都是一样的,只不过这个没有头部只有底部,而且有两个粒子系统:

 

 三、第三个代表性场景

            这个夭折了因为CSDN提示我上传的图片到达上限了,这我是没想到的,没办法这一集只能分为上下两期来讲了,而且下期我将讲述一个非常劲爆的敌人,感兴趣的话就等我一两个小时把内容整理完成发出来吧。

        md,我没想到CSDN有规矩24小时内只能上传300张图片,喜提一天冷却时间,大伙只能等我明天再来把这篇和下一篇文章写完了。

总结

        终于过了一天的坐牢期可以发图片出来了,我就把这一期的效果图展示在这里吧。

再来看看阿斯匹德的效果

再来看看孵化虫Hatcher

然后再来看看沃姆worm的效果:

猜你喜欢

转载自blog.csdn.net/dangoxiba/article/details/143031871