提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
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的效果: