提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、第一个代表性场景
- 1.制作敌人僵尸跳跳虫更多敌人
- 2.制作敌人阿斯匹德更多可交互对象
- 3.制作敌人孵化虫和它的孩子
- 二、第二个代表性场景
- 1.制作更多敌人
- 2.制作更多可交互对象
- 总结
前言
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 PoolGameObject 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张图片,喜提一天冷却时间,大伙只能等我明天再来把这篇和下一篇文章写完了。
总结
想看效果的请移步到下一篇文章!!!
想看效果的请移步到下一篇文章!!!
想看效果的请移步到下一篇文章!!!