我来自手机游戏界的巨著

哈Ha! 今天(9月26日)是我的生日,这对我来说,这是发布关于我的难题续集的文章的重要原因。 我警告您,我是一个业余爱好者,这意味着开发的所有方面都会有很多错误(如果找到,请写,我会很乐意考虑在内)。 在本文中,我想告诉我所有的内容(好吧,或者几乎所有内容),我是如何制作续集的,我是如何去做的,以及我后来来的。

为了不引起混淆,在这里我指的是本文中术语的含义:
原始内容是第一部分,是带有技术演示的地下驱动程序的游戏。 你可以在这里阅读。

续集是该系列的第二部分,本文将讨论该游戏。

我会定期将原始游戏与续集进行比较,以强调两者之间的区别。

简要介绍发展

我从1月下旬开始从事游戏工作,到3月底,技术部分已经完成(2个月)。 在我完成另一场比赛后,于五月中旬回到继续开发该游戏。 我在暑假结束时很明显地完成了比赛,这整个过程(3.5个月)让我充满了游戏的内容。 结果,我制作的续集比原始游戏更快(6个月对5.5个月)。

我在统一引擎上做了一个游戏。 我希望这些家伙能够自己开发引擎并继续进行编程,但是出了点问题,仍然决定按标准进行游戏,但要经过我的测试。

在原版和续集之间

制作续集的想法是在原始游戏发布前一个月(八月份的某个时候)出现的。 看到我犯的错误,我想删除所有内容并重新开始工作并取得成功。 但是由于存在很多问题代码,所有内容都准备就绪,我才开始进行任何更改,而我只是推迟了开发。 有必要去释放。

发布后,我再次被续集的想法所折磨。 这次我没有开始,因为我在道德上是懒惰的,所以在发布之后它是完全柔软的。 我想要一些新颖有趣的东西。 大规模实验开始了。

在接下来的3个月中,我尝试在游戏中实现任何想法,任何设置,任何概念。 尽管有雄心壮志,执行困难,但有时我还是这样做,尽管有逻辑和常识。 结果,我得到了大约50个项目。 它们都是不同的类型:从射击游戏到策略游戏,从平台游戏到rpg游戏。

因此实验将继续进行,直到我累了。 而且我厌倦了不制作游戏,而是厌倦了自己制作的游戏。 我给了自己一个目标:在比赛结束前一周至少进行一些游戏。 于是我的第二场比赛出现了。

Pro 2游戏
这款游戏同时非常简单和复杂。 有必要剪线而不是剪线图。 游戏的意思是每个切割线都被2除,并且在其中心出现了一个图形。 游戏的特点是所有几何图形都是动态的。 图移动了,线总是连接某些图。




在此之后,我很上进(很上进),并准备进行一个新项目。 我感到力量激增,但仍然继续着自己的游戏续集。

主意

在开始做某事之前,我决定全面浏览一下原件。 吓坏了。 源于质量。 游戏大部分都被标准谜题所割裂:需要解锁关卡,收集星星,计时器,完成,但是所有这些都是在没有预算的情况下完成的,而且非常鸡肋。 原来真的缺少动画! 尽管其中包含一些原始的东西,但也许是真诚的。 尽管即使在这里,他们也设法领先于我。

我发现类似的东西
事实证明,有一个非常相似的游戏,名称几乎相同。 她看起来像是我的游戏中比较成功的一种。 我从这段视频中发现了她。
之后,我发现这款游戏是LG智能电视的独家产品。 它由LG R&D Lab的俄罗斯分部于2014年创建:



它由遥控器上的“左”和“右”箭头控制。 以与我的游戏相同的方式(屏幕的2个部分)。 我能说的是,倾斜角度是相同的-30°。 从技术上讲,可以说我的游戏被窃了。 虽然我在发布第一款游戏大约两个月后就了解了她。




理解原始版本非常糟糕的位置后,我决定通过重大更改使游戏恢复活力,以使其更好。 然后幻想飞了起来:让它有一个情节,其中会有一个选择,会有老板经过周到的攻击,会有原始人所缺乏的作品,会有一次完整冒险的感觉,等等。 总的来说,在原创和续集之间的这段时间里,我想到了所有最好的想法。 所有不起作用或效果不佳的东西注定要我丢掉续集。

第一个演示

这一切当然都是从他开始的。 我决定:“如果解决问题,那就彻底解决。” 这种变化的第一个受害者是管理。 我可以从类似的游戏中窃取它(见上文)。 这正是我最初想要的管理层,但不知道该怎么做。 补充内容非常简单:每次单击时只需添加旋转动画。 但这不适合我。 至少对于与在类似游戏中一样,要感觉到控件,必须制作相同的静态摄像机,并随着游戏的进行而明显降低游戏级别。 但是我想要动作,动态和速度,所以我对原始控件进行了合理的开发。 现在,不再需要按压和旋转一定程度,而是进行了夹紧,最终旋转的程度取决于其持续时间。 它看上去显然比原始版本更好。

这是因为我通常控制了控件,所以原来的主要错误消失了,现在可以使水平比原来的加载更多,而不必担心出现滞后和发条。 然后是实验部分。

图形演示

我从来不知道如何绘制普通图形,几乎总是用技术部分代替它,或者说是它的常规执行方式。 这个游戏也不例外。 代替简单的普通精灵,出现了逼真的光线。 这是二维光的幻觉。 实际上,这是在金属表面上的三维光,并且所有对象都具有带有特定着色器的材质。 看起来不错:



在测试中,它显示稳定的60 fps,但是在电话上,甚至在我的索尼xperia上,它的速度都约为20 fps,下降到10 fps。 我遇到了表演上限。 我不得不走另一条路,破坏性的道路...

破坏性

最初,这对我来说似乎不是一个好主意。 但是我决定尝试,现在这是我的主要游戏节目。 根据计划,我再次希望获得更多的真实感,即根据影响的方向和强度破坏生成的碎片。 但是,这次,我的知识再次使计划搁在了天花板上。 我不得不简化为一个简单的。

现在,可破坏性基于更简单的操作原理,即,它仅从物理片段中创建了自己的副本,原始对象删除了SpriteRenderer,Collider2D的组件,如果有的话,则禁用了Rigidbody2D。

但是另一个问题出现了-对撞机。 一方面,您可以使用PolygonCollider2D而不被折磨,但另一方面,您将不得不在以后的游戏设计和优化中受苦。 因此,所有被破坏块的片段都具有BoxCollider2D(甚至圆形物体的片段)。

而且,通过正确设置时间固定步长参数(它等于0.0(3)或每秒30),对优化做出了重大贡献。 但是现在,物体以高速度飞过,这无疑影响了游戏设计。

这些元素使优化达到了可接受的水平,现在舞台上可能有多达数百个物理对象! 在原始版本之后,绝对是突破,革命等。 意识到自己朝着正确的方向发展后,我决定解决另一个长期存在的游戏问题:压倒性的硬核。 为了以某种方式引起我的兴趣,我做了...

损害制度

对我来说,这是开发中最晦涩的部分,已被重写2次。 正在进行的工作。 结果,出现了一个极其复杂的系统,但它的工作范围很广。

但首先,值得一提的是损害感知在这里的工作原理。 似乎它的工作原理是“击球越重,伤害越强”,但事实并非如此。 在大多数情况下,它是根据“接触时间越长-破坏越大”的原理工作的,其中“冲击力”等重要内容的位置由损坏系数代替,损坏系数根据情况针对每个造成损坏的对象手动配置。 发生这种情况的原因是固定时间的步长太大,以至于创建了一个强大的错误:游戏无法处理Enter2D。 这就造成了类似这样的情况:高速坠毁-没有受到损坏。 我为什么不修复它? 即使我也不能这么说。

那么,损害赔偿制度从哪里开始呢? 从健康。 玩家的生命值等于1(后来增加到2)。 是的,这还不够,在与陷阱的第一次强烈接触中他会死,但至少在低速下有幸存的机会(甚至几次)。 我不想更改原始的。 “但是什么会对玩家造成伤害?” -我想出了主要陷阱。

主要陷阱

我困惑的基础是陷阱,但它们与游戏的名称背道而驰。 从名称可以看出,游戏应该是关于在重力作用下掉落的球。 但是没有那么多。 相反,存在更多标准难题。

主要和第一个是锯。 简单清晰的难题。 它的编写不是很理想,在后期制作期间,我修复了它。


锯脚本
using UnityEngine; public class Saw : GlobalFunctions { public AudioClip setClip; private TypePlaying typePlaying = TypePlaying.Sound; private AudioBase audioBase; private float speed = 4f; private Transform tr; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); tr = transform; } private void Update() { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, tr.localEulerAngles.z - speed * s); } private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == "Player") { audioBase.SetSound(setClip, 1, 0.2f, typePlaying, false); } } public float GetSpeed() { return speed; } } 


接下来是一台激光,它非常重地装载了所有东西。 如果您在舞台上放40张,游戏将开始明显滞后。 但是我也渴望添加成熟的光线物理定律,即反射甚至折射。 但是没有时间,我没有完成。 尽管我做了一些优化,但并没有太大帮助。


激光脚本
 using UnityEngine; public class Laser : MonoBehaviour { public Vector2 vector2; public bool active = true; public GameObject laserActive; public LineRenderer lr1; public Transform tr; public BoxCollider2D bcl; public Damage dmg; private void Start() { lr1.startColor = lr1.endColor = LaserColor(); } public Color LaserColor() { Color c = new Color(0f, 0f, 0f, 1f); switch (dmg.GetTypeLaser().Type2int()) { case 1: c = new Color(1f, 0f, 0f, 1f); break; case 2: c = new Color(0f, 1f, 0f, 1f); break; case 3: c = new Color(0f, 0f, 0f, 0.4901961f); break; case 4: c = new Color(1f, 0.8823529f, 0f, 1f); break; case 5: c = new Color(0.6078432f, 0.8823529f, 0f, 1f); break; case 6: c = new Color(1f, 0.2745098f, 0f, 1f); break; } return c; } private void Update() { LaserUpdate(); } private void LaserUpdate() { if (active == true) { Vector2[] act1 = Points(tr.position, -tr.up); lr1.SetPosition(0, act1[0]); lr1.SetPosition(1, act1[1]); bcl.size = new Vector2(0.1f, 0.1f); bcl.offset = act1[2]; } return; } private Vector2[] Points(Vector2 start, Vector2 end) { Vector2[] ret = new Vector2[3]; RaycastHit2D hit = Physics2D.Raycast(tr.position, -tr.up, 200f); ret[0] = tr.position; ret[1] = hit.point; vector2 = ret[1]; float distance = Vector2.Distance(tr.position, hit.point); bcl.size = new Vector2(0.1f, 0.1f); if (hit.collider == bcl) { ret[2] = new Vector2(0f, 0.5f); } else { ret[2] = new Vector2(0f, -distance - 0.2f); } return ret; } } 



炸弹是最后一个陷阱,在添加炸弹之前,我重新编写了损害承受系统,特别是将与玩家健康相关的所有内容都转移到了单独的HealthBar脚本中(可用于其他目的)。 炸弹仍然出现后,其物理特性尚待改进,在此过程中再次完成。 最后,结果再次值得。


爆炸脚本
 using System.Collections; using UnityEngine; public class Explosion : GlobalFunctions { public float power = 1f; public float radius = 5f; public float health = 20f; public float timeOffsetExplosion = 1f; public GameObject[] contacts = new GameObject[0]; public Animator expAnim; public bool writeContacs = true; public AudioClip setClip; private float timeOffsetExplosionCount; private float alphaTimer; private bool isTimerOn = false; private bool firstAPEvirtual = true; private Collider2D cl; private Rigidbody2D rb; private SpriteRenderer sr; private AudioBase audioBase; private Explosion explosion; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); cl = GetComponent<Collider2D>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); explosion = GetComponent<Explosion>(); } private void Start() { alphaTimer = sr.color.a; StartCoroutineTimerOffsetExplosion(); } private void OnCollisionEnter2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; GameObject[] n = new GameObject[cont + 1]; if (cont != 0) { for (int i = 0; i < cont; i++) { n[i] = contacts[i]; } } n[cont] = collision.gameObject; contacts = n; } } private void OnCollisionExit2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; if (cont != 1) { int counter = 0; GameObject[] n = new GameObject[cont - 1]; for (int i = 0; i < cont; i++) { if (contacts[i] != collision.gameObject) { n[counter] = contacts[i]; counter++; } } contacts = n; } else { contacts = new GameObject[0]; } } } public void ActionExplosionEmulation(GameObject obj) { float damage = 0f; if (obj.CompareTag("Laser")) { damage = obj.GetComponent<Damage>().senDamage; } else { damage = obj.GetComponent<Power>().power; } health = health - damage; StartCoroutineTimerOffsetExplosion(); return; } public void StartCoroutineTimerOffsetExplosion() { if (health <= 0f && isTimerOn == false) { isTimerOn = true; timeOffsetExplosionCount = timeOffsetExplosion; StartCoroutine(TimerOffsetExplosion(0.1f)); } } private IEnumerator TimerOffsetExplosion(float timeTick) { yield return new WaitForSeconds(timeTick); timeOffsetExplosionCount = timeOffsetExplosionCount - timeTick; if (timeOffsetExplosionCount > 0f) { float c = timeOffsetExplosionCount / timeOffsetExplosion; sr.color = new Color(1f, c, c, alphaTimer); StartCoroutine(TimerOffsetExplosion(timeTick)); } else { ExplosionAction(); } } private void ExplosionAction() { rb.gravityScale = 0f; rb.velocity = Vector2.zero; audioBase.SetSound(setClip, 2, 1f, TypePlaying.Sound, false); Destroy(cl); CircleCollider2D c = gameObject.AddComponent<CircleCollider2D>(); c.isTrigger = true; c.radius = radius; tag = "Explosion"; if (PlayerPrefs.GetString("graphicsquality") != "high") { Destroy(sr); StartCoroutine(Off()); } else { expAnim.enabled = true; StartCoroutine(Off2High()); } } public IEnumerator Off() { yield return new WaitForSecondsRealtime(0.1f); gameObject.SetActive(false); } public IEnumerator OffHigh(CircleCollider2D c) { yield return new WaitForSecondsRealtime(0.1f); c.enabled = false; } public IEnumerator Off2High() { yield return new WaitForSecondsRealtime(1.5f); gameObject.SetActive(false); } public void APEvirtual() { int cont = contacts.Length; if (cont != 0 && firstAPEvirtual == true) { firstAPEvirtual = false; for (int i = 0; i < cont; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<PhysicsEmulation>()) { contacts[i].GetComponent<PhysicsEmulation>().ExplosionPhysicsEmulation(explosion); } } } } } public void AnimFull() { sr.color = new Color(1f, 1f, 1f, 1f); sr.size = new Vector2(3f * radius, 3f * radius); return; } } 


看了整个损坏系统后,我决定彻底重写它。 这次,Damage将所有可能的伤害变化放入一个Damage脚本中,并对可破坏的块制作了类似的ActionPhysicsEmulation方法(最后,针对每种单独的伤害类型,编写了自己的优化方法)。 同样,损坏的强度取决于对象的“强度”(脚本仅在播放器上)的强度。

最后,只有这三个谜题比原始谜题少了一些。 但这并不是停止的理由:我也没有忘记在整个开发过程中进行试验。 它出现了。

力场(禁用重力,减速并缓慢杀死)


脚本速度场
 using UnityEngine; public class VelocityField : GlobalFunctions { public float percent = 10f; public float damage = 0.01f; public float heal = 0.01f; public GameObject[] contacts = new GameObject[0]; private HealthBar hb; private void Awake() { hb = GameObject.FindWithTag("MainCamera").GetComponent<Management>().healthBar; } private void FixedUpdate() { if (contacts.Length != 0) { for (int i = 0; i < contacts.Length; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<Rigidbody2D>()) { float s = Time.fixedDeltaTime / 0.03f; Vector2 vel = contacts[i].GetComponent<Rigidbody2D>().velocity; contacts[i].GetComponent<Rigidbody2D>().velocity = vel / 100f * (100f - percent * s); } } else { contacts = Remove(contacts, i); } } } } private void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Add(vi.fields, gameObject); rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(300f); } if (collision.tag == "Player") { hb.StartVFRad(damage); } contacts = Add(contacts, collision.gameObject); } } } public void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Remove(vi.fields, gameObject); if (vi.fields.Length != 0) { rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; } else { rb2.gravityScale = 1f; rb2.freezeRotation = false; vi.inVelocityField = false; } if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(60f); } if (collision.tag == "Player") { hb.EndVFRad(heal); } contacts = Remove(contacts, collision.gameObject); } } } } 


践踏(他杀死了球员,将他们压死了)

流浪汉脚本
 using UnityEngine; public class TrampAnim : MonoBehaviour { public float speed = 0.1f; public float speedOffset = 0.01f; public float damage = 1f; private float sc; private float maxDis; public Vector3 start; public Vector3 end; public TrampAnim ender; public bool active = true; public bool trigPlayer = false; private AudioSet audioSet; private bool audioAct; private Transform tr; private HealthBar hb; public int count = 0; public void Start() { if (active) { tr = transform; maxDis = Vector2.Distance(start, end); sc = Vector2.Distance(tr.localPosition, start) / maxDis; hb = Camera.main.GetComponent<Management>().healthBar; audioAct = GetComponent<AudioSet>(); if (audioAct) { audioSet = GetComponent<AudioSet>(); } } } public void Update() { if (active) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); if (count == 0) { tr.localPosition = Vector2.MoveTowards(tr.localPosition, end, (speed * sc + speedOffset) * s); if (tr.localPosition == end) { count = 1; if (trigPlayer && ender.trigPlayer) { hb.Damage(100f, tag, Vector2.zero); } if (audioAct) { audioSet.SetMusic(); } } } else { tr.localPosition = Vector2.MoveTowards(tr.localPosition, start, (speed * sc + speedOffset) * s); if (tr.localPosition == start) { count = 0; } } sc = Vector2.Distance(tr.localPosition, start) / maxDis; } } public void OnCollisionEnter2D(Collision2D collision) { Transform trans = collision.transform; string tag = trans.tag; if (tag == "Player") { trigPlayer = true; } else if (active == false) { if (trans.GetComponent<PhysicsEmulation>()) { trans.GetComponent<PhysicsEmulation>().TrampAnimPhysicsEmulation(GetComponent<TrampAnim>()); } } } public void OnCollisionExit2D(Collision2D collision) { string tag = collision.transform.tag; if (tag == "Player") { trigPlayer = false; } } } 


辐射(缓慢降低健康状况)


脚本辐射
 using System.Collections; using UnityEngine; public class Radiation : MonoBehaviour { public bool isActiveRadiation = false; private Management m; private HealthBar hb; private void Awake() { gameObject.SetActive(PlayerPrefs.GetString("ai") == "off"); m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); hb = m.healthBar; } private void Start() { StartCoroutine(RadiationDamage()); } public IEnumerator RadiationDamage() { yield return new WaitForSeconds(0.0002f); if (isActiveRadiation) { hb.StraightDamage(0.0002f, "Radiation"); } StartCoroutine(RadiationDamage()); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = true; hb.animator.SetBool("isVisible", true); } } public void OnTriggerExit2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = false; hb.animator.SetBool("isVisible", false); if (hb.healthBarImage.fillAmount == 0f) { m.StartGraphics(); } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hb.animator.SetBool("isVisible", false); PlayerPrefs.SetString("ai", "on"); gameObject.SetActive(false); } } } 


陷阱(被触摸时会杀死的蓝色球,指的是“世界上最困难的游戏”)


脚本DeathlessScript
 using UnityEngine; public class DeathlessScript : MonoBehaviour { private HealthBar hb; private void Awake() { hb = Camera.main.GetComponent<Management>().healthBar; } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { hb.Damage(10f, tag, Vector2.zero); } } } 


我没有在“损害”脚本中记录所有这些类型的损害,但通常用拐杖都能正常工作。 此后,其他技术人员加入了队伍。

其他机制

他们变得多样化。 它们很多,因此它们都很有趣,还不足以与大多数游戏机制进行交互。

最早的这类机械师是盖茨。 最重要,最实用的功能。 在需要功能障碍的所有位置绝对有用。 它还具有其他功能:isActive用于确定启动状态,isState以便在激活后固定位置(名称混合在一起,但是当我注意到它来不及固定时)。


脚本门
 using UnityEngine; using System.Collections; public class Gate : MonoBehaviour { [Header("StartSet")] public Vector2 gateScale = new Vector2(1, 4); public float speed = 0.1f; public bool isReverse = false; public bool isEnd = true; public Vector2 animSetGateScale = new Vector2(); public Vector2 target = new Vector2(); [Header("SpriteEditor")] public Sprite mainSprite; [Header("Assets")] public GameObject door1; public GameObject door2; private IEnumerator fixUpdate; private void Start() { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); ds1.sprite = mainSprite; ds2.sprite = mainSprite; if (isReverse == false) { animSetGateScale = target = gateScale; } fixUpdate = FixUpdate(); SetGate(animSetGateScale); } private IEnumerator FixUpdate() { yield return new WaitForSeconds(0.03f); if (animSetGateScale != target) { float s = Time.fixedDeltaTime / 0.03f; animSetGateScale = Vector2.MoveTowards(animSetGateScale, target, speed * s); SetGate(animSetGateScale); StartCoroutine(FixUpdate()); } } private void SetGate(Vector2 scale) { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); Vector2 size = new Vector2(mainSprite.texture.width, mainSprite.texture.height); float k = size.x / size.y; ds1.size = new Vector2(gateScale.x, scale.y / 2f); ds2.size = new Vector2(gateScale.x, scale.y / 2f); BoxCollider2D d1 = door1.GetComponent<BoxCollider2D>(); BoxCollider2D d2 = door2.GetComponent<BoxCollider2D>(); d1.size = new Vector2(gateScale.x, scale.y / 2f); d2.size = new Vector2(gateScale.x, scale.y / 2f); door1.transform.localScale = new Vector3(1f, 1f, 1f); door2.transform.localScale = new Vector3(1f, 1f, 1f); door1.transform.localPosition = new Vector3(0f, (gateScale.y / 2f) - (scale.y / 4f), 0f); door2.transform.localPosition = new Vector3(0f, -(gateScale.y / 2f) + (scale.y / 4f), 0f); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player")) { if (isReverse == false) { target = Vector2.zero; } else { target = gateScale; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } private void OnTriggerExit2D(Collider2D collision) { if (collision.CompareTag("Player") && isEnd == true) { if (isReverse == false) { target = gateScale; } else { target = Vector2.zero; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } } 


物理对象拥有类似的功能。 不,这些不是来自破坏的对象,它们只是物理对象(尽管它们也可以被破坏,但并未使用此机制)。 它们中的难题并不多,但它们与其他机制很好地结合在一起。 例如,使用门:当对象触摸门触发器时,门将打开。

自从我学会了“拥有权力”以来,多达三位机械师对其进行了控制。 这些是触发器,具有与对象进行交互的相同代码,但是每个触发器都以自己的方式执行任务。 第一个是力场(它使物体减速,将力乘以某个系数)。 在该点的方向上第二增加的强度和该点具有“重力”。 第三个是偶然生成的:当与零重力有关的难题不起作用时,此脚本将其保存。 在其中,对象改变了力的方向,而没有改变它本身,其强度。


如何运作
首先,根据勾股定理,计算斜边,斜边是向量的系数,对于恢复强度很有用。 然后使用Atan2函数计算角度。 之后,将offsetAngle添加到拐角,并基于正弦和余弦构造一个新的矢量,将其乘以系数,并在不改变力的情况下获得改变的方向。
 public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; } 


在此基础上,我对附加演员的整个幻想消失了。 是的,有一些想法,例如绳索,缆车上的炸弹等。 但是随后出现了正常的想法:您必须再次渲染游戏。 尽管如此,我对自己还是诚实的:绝大多数人都在玩手机游戏,而且如果游戏过于复杂,几乎没人会玩我的游戏。 我决定从一击就杀死玩家的谜题开始,但由于可破坏性,我不想改变伤害。 然后是正常的附加机制的想法:增强器或修改器。

根据该概念,他们进行了一些与某些基本值相关的临时改进。 共有5种助推器:治疗,永生,时间膨胀(缓慢移动),重力变化和玩家体重变化。

但这似乎是一种标准:触发球散布在水平面上以辅助传球。 因此,我将这些助推器添加到了激光器中。 改变了一些机制,它起作用了。


现在,激光具有与玩家互动的5种模式:伤害与治疗,永生,时间膨胀(缓慢移动),重力变化和玩家质量变化。 这是同一件事,但有一个区别:激光不断作用于播放器,如果您离开激光,效果将立即消失(或一段时间后)。 是的,助推器几乎相同,但是激光不是标准的(所以整个游戏都是如此)。

游戏的物理主题使创建蹦床成为可能,该蹦床通常用于分散玩家然后摧毁墙(尽管这是带有PhysicsMaterial的简单BoxCollider2D,其反弹参数针对不同的反弹强度而扭曲)。

游戏的桑迪风让您可以创建自己的动画脚本。 基本上,他们从一点到另一点移动对象或旋转对象。 以前,它们具有更多功能:动画(按点)旋转对象,更改比例(按点),为对象动画的开始和结束提供更精确的标签等功能。 但是由于这些都是虚构的事实,它们疯狂地消耗了生产力,因此我不得不以优化的名义将其删除。 动画脚本可用于需要显示简单动画的任何地方,因为正如我所说:“原始动画非常缺乏!” 只有两个脚本:

BasicAnimation和PointsAnimation。

基本动画脚本
 using UnityEngine; using System.Collections; public class BasicAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeed = 0.05f; public float rotation = 0f; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; private float rotationActive = 0f; public void SetPos(bool pos, float m) { rotationActive = rotation * (pos ? 1 : m); } private void Start() { tr = transform; animMake = false; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { rotationActive = rotation; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; rotationActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (rotationActive != rotation) { rotationActive = Mathf.MoveTowards(rotationActive, rotation, speedSpeed); } else { animMake = false; isMoved = false; } } else { if (rotationActive != 0f) { rotationActive = Mathf.MoveTowards(rotationActive, 0f, speedSpeed); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make == true) { float rot = tr.localEulerAngles.z; float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, rot + rotationActive * s); } } } 

PointsAnimation脚本
 using UnityEngine; using System.Collections; public class PointsAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeedPosition = 0.001f; public float speedPosition = 0.1f; public Vector3[] pointsPosition = new Vector3[0]; public int counterPosition = 0; private float speedPositionActive = 0f; private int pointsPositionLength = 0; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; public void SetPos(bool pos, float m) { speedPositionActive = speedPosition * (pos ? 1 : m); } private void Awake() { pointsPositionLength = pointsPosition.Length; tr = transform; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { speedPositionActive = speedPosition; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; speedPositionActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (speedPositionActive != speedPosition) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, speedPosition, speedSpeedPosition); } else { animMake = false; isMoved = false; } } else { if (speedPositionActive != 0f) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, 0f, speedSpeedPosition); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make) { if (tr.localPosition == pointsPosition[counterPosition]) { counterPosition++; if (counterPosition == pointsPositionLength) { counterPosition = 0; } } else { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localPosition = Vector3.MoveTowards(tr.localPosition, pointsPosition[counterPosition], speedPositionActive * s); } } } } 


用户界面

与原始版本相比,这是一个真正的杰作。

为了进行比较,以下是原始内容:


这是续集:


这是原始文件:


这是续集:


在这里orig ...我认为这很清楚。 我想到的续集中的极简主义,而不是颜色不适当的暂停按钮和坦率的计时器干扰,现在在左下角提供了一个洛可可式的,不知何故的暂停按钮。 续集仍然赢得菜单。 与原始图像不同,到处都是动画,背景是我在“着色器图形”中不小心写的11个着色器。 功能也越来越好,有图形设置,单独的声音和音乐设置,允许您更改保存的控制台-在原始菜单中没有任何设置。

结果真好,因为我决定看其他游戏。 总的来说,我到处都从那里偷走了最好的东西。 这是我的特别之处:

  1. 播放菜单
    取材于Alto的Adventure,只有经历变成了嘲笑,笑话,讽刺性评论等。
  2. 暂停
    同样来自Alto,但不是那么实用,但是它更方便安装,玩起来更方便。
  3. 设定值
    部分取自Vector 2,即菜单和音量滑块的形式。
    他总体上花了一点时间,但其他方面则独自完成。

主控台

首先,对保护的工作方式有所保留。 有两个变量负责全局和局部保护:分别是进度数和电梯保存数。 进度变量负责场景之间的保存,而电梯保存变量负责场景内的保存。 当您按下“开始”或“重新开始”按钮时,游戏会将进度转移到场景中,并产生一个保存在电梯保存编号下的玩家。

控制台允许您更改或创建任何变量。 如此简单而强大的工具对我来说对测试游戏和确定游戏中的错误非常有用。 控制台本身是模仿其他控制台的手写命令。

脚本DebugConsole
 using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using System.Collections; public class DebugConsole : MonoBehaviour { public Animator animatorBlackScreen; public Language l; public InputField inputField; public Text textDebug; private bool access = false; public void AnalyzeText() { string txt = inputField.text.ToLower(); string[] output = new string[0]; string txtLoc = ""; for (int i = 0; i < txt.Length; i++) { if (txt[i] == ' ') { if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } } else { txtLoc = txtLoc + txt[i]; } } if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } Analyze(output); } public void Analyze(string[] commands) { switch (commands[0]) { case "playerprefs": if (access == true) { if (commands.Length < 2) { Log(l.ConsoleLanguage(1));//1 } else { switch (commands[1]) { case "f": case "float": float f = 0f; if (float.TryParse(commands[3], out f)) { PlayerPrefs.SetFloat(commands[2], float.Parse(commands[3])); Log(l.ConsoleLanguage(2, commands[2]));//2 } else { Log(l.ConsoleLanguage(3));//3 } break; case "i": case "int": int i = 0; if (int.TryParse(commands[3], out i)) { PlayerPrefs.SetInt(commands[2], int.Parse(commands[3])); Log(l.ConsoleLanguage(4, commands[2]));//4 } else { Log(l.ConsoleLanguage(5));//5 } break; case "s": case "string": PlayerPrefs.SetString(commands[2], commands[3]); Log(l.ConsoleLanguage(6, commands[2]));//6 break; case "clear": PlayerPrefs.DeleteAll(); SceneManager.LoadScene(0); break; default: Log(l.ConsoleLanguage(7, commands[1]));//7 break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "next": if (access == true) { if (commands.Length > 1) { switch (commands[1]) { case "level": int p = PlayerPrefs.GetInt("progress"); PlayerPrefs.SetInt("progress", p + 1); Log("ok level"); break; case "save": int s = PlayerPrefs.GetInt("elevatorsave"); PlayerPrefs.SetInt("elevatorsave", s + 1); Log("ok save"); break; case "start": PlayerPrefs.SetInt("elevatorsave", 0); Log("ok start"); break; case "end": PlayerPrefs.SetInt("elevatorsave", 1); Log("ok end"); break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "echo": if (commands.Length == 1) { Log(l.ConsoleLanguage(9));//9 } else { switch (commands[1]) { case "vertogpro"://echo vertogpro access = true; Log(l.ConsoleLanguage(10));//10 break; default: Log(l.ConsoleLanguage(11));//11 break; } } break; case "restart": if (access == true) { SceneManager.LoadScene(0); } else { Log(l.ConsoleLanguage(12));//12 } break; case "authors": Log(l.ConsoleLanguage(13));//13 break; case "discharge": animatorBlackScreen.SetBool("isActive", true); PlayerPrefs.SetString("start", "key"); PlayerPrefs.SetString("language", "nothing"); PlayerPrefs.SetString("graphicsquality", "medium"); PlayerPrefs.SetFloat("sound", 0.5f); PlayerPrefs.SetFloat("music", 0.5f); PlayerPrefs.SetFloat("rotatenextlevel", 0f); PlayerPrefs.SetInt("elevatorsave", 0); PlayerPrefs.SetInt("progress", 1); PlayerPrefs.SetInt("deaths", 0); PlayerPrefs.SetInt("discharge", PlayerPrefs.GetInt("discharge") + 1); PlayerPrefs.SetInt("lastmenueffect", -1); PlayerPrefs.SetString("isshotmode", "false"); PlayerPrefs.SetString("boss1", "life"); PlayerPrefs.SetString("boss2", "life"); PlayerPrefs.SetString("ai", "off"); PlayerPrefs.SetString("boss3", "life"); PlayerPrefs.SetString("end", "none"); StartCoroutine(StartGame()); break; case "clear": Clear(); break; case "info": if (access == false) { Log(l.ConsoleLanguage(14));//14 } else { Log(l.ConsoleLanguage(15));//15 } break; default: Log(l.ConsoleLanguage(16, commands[0]));//16 break; } } public void Log(object message) { textDebug.text = message.ToString(); } public void Clear() { inputField.text = ""; textDebug.text = ""; } public string[] Add(string[] old, string addComponent) { string[] n = new string[old.Length + 1]; if (old.Length != 0) { for (int i = 0; i < old.Length; i++) { n[i] = old[i]; } } n[old.Length] = addComponent; return n; } public IEnumerator StartGame() { yield return new WaitForSeconds(1f); SceneManager.LoadSceneAsync(0); } } 


特别是对您而言,我将在其中留下一些流动团队:

  1. 释放-重设游戏进度(以及所有其他信息)
  2. echo vertogpro-提供开发团队访问权限的团队
  3. playerprefs [给定的类型(字符串,整数,浮点数)] [变量名] [数据]-更改或创建任何变量。示例:playerprefs int进度14
  4. next-一个简化的级别导航的子类型,具有自己的命令:
    • 开始-保存在关卡的开头(下一个开始)
    • end-保存在关卡的末尾(下一个末尾)
    • 保存-传送到下一个保存(下一个保存)
    • 等级-传送到下一个等级(下一个等级)

图形

在这一年中,我没有学习绘画的方法,所以我做了与原始图形几乎相同的工作:我为Maycraft下载了大约30个纹理包,选择了每个纹理包中的最好的,从而得到了主要的图形。这些图片与原始图片并没有太大不同,它令我非常生气,令我非常生气,以至于我仍然发现各种动画效果(爆炸,火等),并从资产商店中抽出了各种各样的纹理包。即使对于手机游戏,图形效果也很差,尽管仍在观察中。这是原始文件:


这是续集:


保存

如果保存的原理很简单,那么它们的实现就不是很容易。保存系统包含3个脚本:

  1. ElevatorBase是启动团队出现的基础。在其中,通过liftsave变量从保存数组中选择活动保存。

    脚本电梯库
     using UnityEngine; using System.Collections; public class ElevatorBase : MonoBehaviour { public GameObject[] savers = new GameObject[0]; public float inputStartBlock = 1f; private GameUI gameUI; public void Awake() { int l = savers.Length; if (l != 0) { for (int i = 0; i < l; i++) { if (savers[i] != null) { if (savers[i].GetComponent<Saving>()) { Saving saving = savers[i].GetComponent<Saving>(); saving.isFirst = false; saving.idElevatorBase = i; } else if (savers[i].GetComponent<Elevator>()) { savers[i].GetComponent<Elevator>().isFirst = false; } } } int es = PlayerPrefs.GetInt("elevatorsave"); if (savers[es] != null) { if (savers[es].GetComponent<Saving>()) { savers[es].GetComponent<Saving>().isFirst = true; } else if (savers[es].GetComponent<Elevator>()) { savers[es].GetComponent<Elevator>().isFirst = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); StartCoroutine(BlockEnabled()); GameObject.Find("TipsInput").GetComponent<TipsGamePlayInput>().active = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); gameUI.ChangeisBlocked(); } } public IEnumerator BlockEnabled() { yield return new WaitForSeconds(inputStartBlock); GameObject block = gameUI.block.gameObject; block.SetActive(false); } } 

  2. Saving — , , , elevatorsave id.

    Saving
     using System.Collections; using UnityEngine; public class Saving : MonoBehaviour { public Saving[] savings; public Vector2 startPos; public float startRot; public bool isActive = true; public bool isFirst = true; public int idElevatorBase = 0; public TipsGamePlayInput tgpi; private GameObject player; private GameObject cam; private Transform trp; private GameUI gameui; private Management m; private Saving self; private void Start() { self = GetComponent<Saving>(); cam = GameObject.FindWithTag("MainCamera"); m = cam.GetComponent<Management>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = m.player; trp = player.GetComponent<Transform>(); if (isFirst) { trp.position = startPos; m.Set(startRot); OfferSaves(); } isActive = !isFirst; tgpi.SetActive(!isFirst); StartCoroutine(BlockFalse()); } public IEnumerator BlockFalse() { yield return new WaitForSeconds(1f); gameui.block.gameObject.SetActive(false); } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isActive == true) { isActive = false; PlayerPrefs.SetInt("elevatorsave", idElevatorBase); OfferSaves(); } } public void OfferSaves() { if (savings.Length != 0) { for (int i = 0; i < savings.Length; i++) { savings[i].isActive = false; savings[i].tgpi.SetActive(false); } } } } 

  3. Elevator — , . : ( ).

    Elevator
     using System.Collections; using UnityEngine; public class Elevator : GlobalFunctions { public Vector2 endPos; public Vector2 startPos; public int nextScene = 1; public int nextElevatorSave = 0; public float speed = 0.1f; public bool isFirst = true; public bool isActive = true; public bool isReverse = false; public bool isMake = false; private GameObject player; private Rigidbody2D rb; private Transform tr; private Transform trp; private GameUI gameui; private AudioBase audioBase; private Transform cam; private void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = gameui.m.player; rb = player.GetComponent<Rigidbody2D>(); trp = player.GetComponent<Transform>(); tr = GetComponent<Transform>(); cam = gameui.m.transform; startPos = tr.position; if (isFirst) { trp.position = startPos; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.m.Set(); } else { tr.position = endPos; isMake = true; } isActive = isFirst; isReverse = false; } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isMake == true) { isReverse = true; isActive = true; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.block.gameObject.SetActive(true); PlayerPrefs.SetInt("elevatorsave", nextElevatorSave); gameui.animatorBlackScreenGame.SetBool("isActive", true); audioBase.LowerSound(0.05f, 16, 0, TypePlaying.Music); StartCoroutine(NumSaveRotate()); StartCoroutine(gameui.StartGame(1.5f, nextScene)); } } private IEnumerator NumSaveRotate() { yield return new WaitForSeconds(1.5f); PlayerPrefs.SetFloat("rotatenextlevel", Stable(cam.localEulerAngles.z, -180f, 180f)); } private void FixedUpdate() { if (isActive == true) { float s = Time.fixedDeltaTime / 0.03f; if (isReverse == false) { rb.velocity = new Vector2(); tr.position = Vector2.MoveTowards(tr.position, endPos, speed * s); trp.position = tr.position; if ((Vector2)tr.position == endPos) { isMake = true; isActive = false; rb.gravityScale = 1f; gameui.block.gameObject.SetActive(false); } } else if (isReverse == true) { tr.position = Vector2.MoveTowards(tr.position, startPos, speed * s); trp.position = tr.position; if (tr.position == (Vector3)startPos) { isActive = false; rb.gravityScale = 1f; } } } } } 

游戏设计

这真是一团糟。游戏设计将开发周期从4个月延长到了6个月。游戏中共有34个等级:30个常规,3个头目和1个决赛(等级)。每个普通人我需要2-3天,每个老板2周,最后一个水平则需要一周。为了平衡所有这些,我以如下方式构建它们:10个级别=> 1个boss => 10个级别=> 2个boss => 10个级别=> 3个boss =>最终级别。

当地的水平是我的骄傲。它们是不寻常的,多样的,甚至有点有趣。这些关卡以特定的形式设计,以营造开放感。为此,我什至画了一张地图:


该地图不是最好的绘图和信息,但是它提供了必要的关卡形式的重要信息。最初,计划是在地图上绘制所有级别,但我没有做那些变暗的级别。顺便说一句,这是一张尺寸为1000x1000像素的地图,正是从这张地图中得出了比例:1个块= 1个像素=播放器大小。

在关卡之间,玩家穿过电梯。它可以交付到任何级别,因此可以在各个级别之间移动,从而使玩家对世界的开放感有了更大的了解。而且,在某些地方,触发器被隐藏起来以激活秘密电梯,该电梯可以前进10-15级。

对于普通级别,有一种构造算法:

  1. 具有地图上形状和比例的背景
  2. 外墙(由于特殊的物理原因,厚度增加了三倍)
  3. 墙是内部的
  4. 关卡自己
  5. 电梯,保存和音频触发器

对于上司而言,情况更为复杂,因为每个上司都同时表现出不同且相似的行为方式。所有老板的生命值均为100,每个等级都有破坏力。最好分别谈论每个:

1个老板的行为非常简单:随机在房间内移动,等待5秒钟然后重复。老实说,这是老板的一个坏榜样:简单,落后和难忘。而且,只有殴打他才能杀死他。但是有4种锯子的形式的防御:3个锯子巧妙地在房间周围随机移动,其中1个在老板移动时保护老板。死亡后会爆炸。

脚本老板管理1
 using UnityEngine; using System.Collections; public class BossManagement1 : GlobalFunctions { public float hp = 100f; public float speed = 0.2f; public bool startActivated = false; public bool activated = false; public bool activatedSaw = false; public bool activatedAngle = false; public bool activatedCoroutine = true; private bool active; private float maxhp; public Vector2 target; public Vector2 targetSaw1; public Vector2 targetSaw2; public Vector2 minBorder; public Vector2 maxBorder; public DeadBoss1 deadBoss; public GameObject backGround; public GameObject healthBar; public Transform tr; public Transform sawMain; public Transform saw1; public Transform saw2; public Arrow arrow; public AudioSet setStart; public AudioSet setEnd; public Transform player; public Power playerPower; private Transform bg, hb; private float targethp = 0f; private Vector2 startMove = new Vector2(-20f, 0f); public void Awake() { maxhp = hp; bg = backGround.transform; hb = healthBar.transform; } public void Start() { if (PlayerPrefs.GetString("boss1") == "death") { Dead(false); } } public void FixedUpdate() { if (startActivated && !activatedCoroutine) { if ((Vector2)tr.position != startMove) { tr.position = Vector2.MoveTowards(tr.position, startMove, speed); saw1.position = Vector2.MoveTowards(saw1.position, startMove, speed); saw2.position = Vector2.MoveTowards(saw2.position, startMove, speed); } else { activatedCoroutine = true; startActivated = false; StartCoroutine(ActivatedOn()); } } if (activated) { if ((Vector2)tr.position != target) { tr.position = Vector2.MoveTowards(tr.position, target, speed); } else { activated = false; sawMain.localScale = new Vector2(0f, 0f); StartCoroutine(TargetRotate()); } } if (activatedSaw) { if ((Vector2)saw1.position != targetSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, targetSaw1, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw1 = new Vector2(x, y); } if ((Vector2)saw2.position != targetSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, targetSaw2, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw2 = new Vector2(x, y); } } if (activatedAngle) { Vector2 dir = player.position - tr.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; tr.localEulerAngles = new Vector3(0f, 0f, Mathf.LerpAngle(tr.localEulerAngles.z, angle, 0.1f)); } } public IEnumerator TargetRotate() { yield return new WaitForSeconds(3f + 3f * hp / maxhp); sawMain.localScale = new Vector2(6f, 6f); float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); target = new Vector2(x, y); activated = true; } public IEnumerator ActivatedOn() { yield return new WaitForSeconds(3f); sawMain.localScale = new Vector2(6f, 6f); target = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw1 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw2 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); activatedSaw = true; activated = true; arrow.isActive = true; } public IEnumerator ActivatedCoroutineOff() { yield return new WaitForSeconds(1f); activatedCoroutine = false; activatedAngle = true; } public void Update() { if (active == true) { if (hp != targethp) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); hp = MoveToward(hp, targethp, speed * s, new Vector2(-0f, maxhp)); } else { active = false; if (targethp == 0f) { Dead(true); } } } UpdateHP(); } public void UpdateHP() { float h = hp / maxhp; bg.localScale = new Vector3(5f, 0.9f, 1f); hb.localScale = new Vector3(4.8f * h, 0.7f, 1f); hb.localPosition = new Vector3(-2.4f + 4.8f * h / 2f, 0f, 0f); } private bool oneTimeMusic = true; public void Damage(float damage) { if (oneTimeMusic == true) { oneTimeMusic = false; deadBoss.StartBoss(); deadBoss.Boom(); setStart.SetMusic(); startActivated = true; StartCoroutine(ActivatedCoroutineOff()); } if (hp != 0f) { targethp = Stable2(hp - damage, 0f, maxhp); speed = speed + damage * 0.02f; active = true; } } public void Dead(bool boom) { active = false; activated = false; activatedSaw = false; startActivated = false; activatedAngle = false; activatedCoroutine = false; backGround.SetActive(false); healthBar.SetActive(false); sawMain.gameObject.SetActive(false); saw1.gameObject.SetActive(false); saw2.gameObject.SetActive(false); setEnd.SetMusic(); arrow.obj.SetActive(false); PlayerPrefs.SetString("boss1", "death"); deadBoss.Dead(tr.position, boom); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { Damage(playerPower.power); } } } 


2老板的素质已经不错,但仍远非理想。他的模式更为复杂:他确定了玩家的位置并确定了他所在的区域。老板选择区域中的随机点并移至该点后。他的辩护已经更加有意义:老板的健康有多个阶段,每个阶段都有不同的武器:

  1. 远处有两把锯
  2. 受锯保护时,相隔2个锯
  3. 2个激光限制,在移动过程中受到电锯的保护
  4. 2台激光器,当用锯保护时
  5. 2个激光器,在移动时受锯和2个锯保护

同样,从图形部分来看,第二个老板比第一个要好:如果在房间的中央激活了触发器,则恢复呆滞形式的不活动时间和取消激活凸台激光器的形式的第三方活动。

脚本老板管理2
 using System.Collections; using UnityEngine; public class BossManagement2 : GlobalFunctions { public float hp = 100f; public float speed = 0.5f; public float speedRotate = 0.5f; public int stage = 1; public bool isAlive = true; public bool isActivated = false; public bool isMove = false; public bool isWorkingLaser = true; private float timeStamina = 0f; private float timeRetarget = 0f; public Vector2 region = Vector2.zero; public Vector3 target = Vector3.zero; public GameObject player; public Transform saw; public Transform laser1; public Transform laser2; public Laser laserL1; public Laser laserL2; public Transform laserOffset1; public Transform laserOffset2; public Explosion explosion; public GameObject explosionAsset; public CircleCollider2D trigStart; public BoxCollider2D laserDetected1; public BoxCollider2D laserDetected2; public GameObject saw1; public GameObject saw2; public Transform health; public Transform stamina; public SpriteRenderer srStamina; private Transform pl; private Transform tr; public Transform state; public Laser state1; public Laser state2; public Laser state3; public Laser state4; private Coroutine coroutineStamina; public SpriteRenderer bossBase; public SpriteRenderer laserD1; public SpriteRenderer laserD2; public Gate gateStart; public Gate gateEnd; public GameObject blockWin; public GameObject physicsIn; public GameObject stateLasers; public GameObject expStart; public AudioSet setStart; public AudioClip setEnd; public AudioBase audioBase; public void Awake() { bool isDeath = PlayerPrefs.GetString("boss2") == "death"; blockWin.SetActive(false); if (isDeath) { isAlive = false; gateStart.isReverse = true; gateEnd.isReverse = true; physicsIn.SetActive(false); stateLasers.SetActive(false); expStart.SetActive(false); gameObject.SetActive(false); } else { tr = transform; pl = player.transform; timeStamina = 5.4f / speedRotate / 100f; timeRetarget = 5.4f / speedRotate; saw.localScale = Vector3.zero; stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 0f); saw1.SetActive(false); saw2.SetActive(false); LaserDisable(); LaserBlockEnable(); } } public void Update() { if (isAlive) { if (isActivated == true) { switch (stage) { case 1: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget1()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 2: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget2()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 3: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget3()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 4: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget4()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 5: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); saw1.SetActive(false); saw2.SetActive(false); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget5()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; } } else { if (trigStart.enabled == false) { isActivated = true; float musicValue = PlayerPrefs.GetFloat("music"); audioBase.UpSound(0.01f, 5, 0, TypePlaying.Music); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); RegionDetected(); LaserDisable(); target = Target(); } } } } public void FixedUpdate() { if (!isMove && isActivated) { laserOffset1.localEulerAngles = new Vector3(0f, 0f, laserOffset1.localEulerAngles.z + speedRotate); laserOffset2.localEulerAngles = new Vector3(0f, 0f, laserOffset2.localEulerAngles.z + speedRotate); if (isWorkingLaser) { state.localEulerAngles = new Vector3(0f, 0f, state.localEulerAngles.z + speedRotate); } } } public void RotatePlayer() { Vector2 p = pl.position; float angle = Mathf.Atan2(py, px) * Mathf.Rad2Deg; laserOffset1.localEulerAngles = new Vector3(0f, 0f, angle); laserOffset2.localEulerAngles = new Vector3(0f, 0f, angle - 180f); } private Vector3[] posLasers = new Vector3[] { Vector3.zero, Vector3.zero}; public void TriggerLaserDefect(int id) { switch (id) { case 1: state1.active = false; state1.lr1.SetPositions(posLasers); break; case 2: state2.active = false; state2.lr1.SetPositions(posLasers); break; case 3: state3.active = false; state3.lr1.SetPositions(posLasers); break; case 4: state4.active = false; state4.lr1.SetPositions(posLasers); break; } if (!state1.active && !state2.active && !state3.active && !state4.active) { isWorkingLaser = false; state1.active = false; state2.active = false; state3.active = false; state4.active = false; laserL1.active = false; laserL2.active = false; laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hp = hp - pl.GetComponent<Power>().power; health.localScale = new Vector2(hp / 50f, hp / 50f); stage = 5 - (int)(hp / 25f); if (stage == 4) { LaserBlockDisable(); } if (hp <= 0f && isAlive == true) { audioBase.LowerSound(0.1f, 50, 0, TypePlaying.Music); audioBase.SetSound(setEnd, 0, 0.8f, TypePlaying.Music, true, 1f); GameObject deadInside = Instantiate(explosionAsset, pl.position, Quaternion.identity); deadInside.GetComponent<Rigidbody2D>().isKinematic = true; deadInside.transform.localScale = new Vector2(2f, 2f); Explosion exp = deadInside.GetComponent<Explosion>(); exp.radius = 2f; exp.health = 0f; exp.timeOffsetExplosion = 3f; exp.StartCoroutineTimerOffsetExplosion(); gateStart.OnTriggerEnter2D(player.GetComponent<Collider2D>()); gateEnd.OnTriggerEnter2D(player.GetComponent<Collider2D>()); PlayerPrefs.SetString("boss2", "death"); blockWin.SetActive(false); gameObject.SetActive(false); } } } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { blockWin.SetActive(true); trigStart.enabled = false; } } public void LaserEnable() { if (isWorkingLaser) { laserL1.active = true; laserL2.active = true; state1.active = false; state2.active = false; state3.active = false; state4.active = false; } laser1.localPosition = new Vector2(0f, -1f); laser2.localPosition = new Vector2(0f, -1f); return; } public void LaserDisable() { if (isWorkingLaser) { state1.active = true; state2.active = true; state3.active = true; state4.active = true; laserL1.active = false; laserL2.active = false; } laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; return; } public void LaserBlockEnable() { laserDetected1.enabled = true; laserDetected2.enabled = true; } public void LaserBlockDisable() { laserDetected1.enabled = false; laserDetected2.enabled = false; } public void RegionDetected() { Vector2 result = Vector2.zero; Vector2 pos = pl.position; if (pos.x > -45f & pos.x <= -30f) { result.x = 1; } else if (pos.x > -30f & pos.x < -5f) { result.x = 2; } else if (pos.x >= -5f & pos.x <= 5f) { result.x = 3; } else if (pos.x > 5f & pos.x <= 30f) { result.x = 4; } else if (pos.x >= 30f & pos.x < 45f) { result.x = 5; } if (pos.y > -45f & pos.y <= -30f) { result.y = 1; } else if (pos.y > -30f & pos.y < -5f) { result.y = 2; } else if (pos.y >= -5f & pos.y <= 5f) { result.y = 3; } else if (pos.y > 5f & pos.y <= 30f) { result.y = 4; } else if (pos.y >= 30f & pos.y < 45f) { result.y = 5; } region = result; return; } private readonly Vector2[] aroundCloser = new Vector2[] { new Vector2(2, 2), new Vector2(2, 3), new Vector2(2, 4), new Vector2(3, 2), new Vector2(3, 4), new Vector2(4, 2), new Vector2(4, 3), new Vector2(4, 4) }; public Vector2 Target() { Vector2 result = Vector2.zero; if (region == new Vector2(3, 3)) { region = aroundCloser[Random.Range(0, 8)]; } switch (region.x) { case 1: result.x = Random.Range(-45f, -32f); break; case 2: result.x = Random.Range(-29f, -5f); break; case 3: result.x = Random.Range(-5f, 5f); break; case 4: result.x = Random.Range(5f, 29f); break; case 5: result.x = Random.Range(32f, 45f); break; } switch (region.y) { case 1: result.y = Random.Range(-45f, -32f); break; case 2: result.y = Random.Range(-29f, -5f); break; case 3: result.y = Random.Range(-5f, 5f); break; case 4: result.y = Random.Range(5f, 29f); break; case 5: result.y = Random.Range(32f, 45f); break; } isMove = true; return result; } public IEnumerator StaminaAnim(float time, int count) { yield return new WaitForSeconds(time); float sc = hp * (100f - count) / 5000f; stamina.localScale = new Vector2(sc, sc); if (count > 1) { count = count - 1; coroutineStamina = StartCoroutine(StaminaAnim(time, count)); } } public IEnumerator Retarget1() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget2() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget3() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget4() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget5() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(true); saw2.SetActive(true); LaserDisable(); RegionDetected(); target = Target(); } } 


3个老板是老板中质量最好的!他使用射线广播四处走动。首先,它随机旋转到任意角度,然后在以不同方向发射的12个射线广播中,选择最长的一个并飞到射线广播的点。该关卡上有一些物体,其中一些也被破坏了。老板射线投射对物体有何反应?触发器被添加到静态对象中,该对象比对象本身大2倍,因此射线广播中有一个点,使凸台不会在空中飞行,不会在墙上,而会铆接到墙上。老板有一个特殊的防御措施:在与老板的关卡开始时(每个老板都是一个独立的大型关卡,没有第三方难题),并且设置了触发器,因此仅激活它们。老板有5个陷阱空白,每个触发器仅使3-4个陷阱处于活动状态。他还拥有一个改进的区域系统,该系统由每个区域(玩家可以在其中)和每个陷阱的预定义区域组成。在飞行中,老板总是杀死玩家。

陷阱列表:

  1. 中心的激光,每次老板开始飞行之后,便开始注视着玩家。
  2. 2个使用Lerp功能移动到指定区域的激光(取决于播放器的位置),并在移动之前发送到播放器(它们应始终在播放器前面,但是出了点问题)。
  3. 始终与玩家位于同一区域的锯。
  4. 2根锯,它们始终从玩家所在的区域指向左侧和右侧。
  5. 4个陷阱球向中心对称移动

脚本老板管理3
 using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class BossManagement3 : MonoBehaviour { public float health = 100f; public Vector4[] boxs = new Vector4[0]; public int[] saw1Fields = new int[0]; public int[] saw2Fields = new int[0]; public int[] saw3Fields = new int[0]; public int[] laser1Fields = new int[0]; public int[] laser2Fields = new int[0]; public Transform trBoss; public SpriteRenderer srBoss; public BossTracing3 bt; public Transform saw1; public Transform saw2; public Transform saw3; public Transform laser; public Transform laser1; public Transform laser2; public Transform trap1; public Transform trap2; public Transform trap3; public Transform trap4; public LineRenderer lr1; public LineRenderer lr2; public TrailRenderer trail; public GameObject exp; public GameObject terminal1; public GameObject terminal2; public GameObject LaserTarget; public GameObject LaserMover; public GameObject TrapsMover; public GameObject SawMover; public GameObject SawsAroundMover; public Explosion explosion; public SpriteRenderer sr; public CircleCollider2D cc; public Animator animatorEnd; public bool isMove = false; public bool isMoveSaw1 = false; public bool isMoveSaw2 = false; public bool isMoveSaw3 = false; public bool isMoveLaser1 = false; public bool isMoveLaser2 = false; public bool isMoveTraps = false; public int loadScene = 35; public int fieldPlayer = 0; private bool isActive = true; private float maxHealth; private Vector2 target = Vector2.zero; private Vector2 saw1target = Vector2.zero; private Vector2 saw2target = Vector2.zero; private Vector2 saw3target = Vector2.zero; private Vector2 laser1target = Vector2.zero; private Vector2 laser2target = Vector2.zero; private Vector2 traptarget1 = Vector2.zero; private Vector2 traptarget2 = Vector2.zero; private Vector2 traptarget3 = Vector2.zero; private Vector2 traptarget4 = Vector2.zero; private Vector2 border = new Vector2(47f, 44.5f); private Vector2 borderSaw = new Vector2(46f, 43.5f); private Management m; public GameObject p { get; private set; } private HealthBar hb; private Transform tr; private Power ppl; private int lengthBoxs = 0; private bool isLife = true; public void Awake() { isActive = !(PlayerPrefs.GetString("boss1") == "life" && PlayerPrefs.GetString("boss2") == "life"); terminal1.SetActive(!isActive); terminal2.SetActive(isActive); trail.enabled = PlayerPrefs.GetString("graphicsquality") != "low"; m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); lengthBoxs = boxs.Length; maxHealth = health; hb = m.healthBar; p = m.player; tr = p.transform; ppl = m.ppl; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); } public void Start() { if (isActive == false) { return; } StartCoroutine(Mover()); fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } TrapMover(); StartCoroutine(Laser1AIM()); StartCoroutine(Laser2AIM()); isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; isMoveLaser1 = true; isMoveLaser2 = true; return; } public void SawMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw1 = true; } public void SawMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw2 = true; } public void SawMover3() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw3 = true; } public void LaserMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser1AIM()); isMoveLaser1 = true; } public void LaserMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser2AIM()); isMoveLaser2 = true; } public void TrapMover() { traptarget1 = new Vector2(Random.Range(-border.x, border.x), Random.Range(-border.y, border.y)); traptarget2 = new Vector2(-traptarget1.x, -traptarget1.y); traptarget3 = new Vector2(-traptarget1.x, traptarget1.y); traptarget4 = new Vector2(traptarget1.x, -traptarget1.y); isMoveTraps = true; } public IEnumerator Laser1AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser1.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Laser2AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser2.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Mover() { yield return new WaitForSeconds(7.5f); if (isLife) { Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); target = bt.GetPosRaycast(); isMove = true; } } public void Update() { if (isActive == false) { return; } float s = Time.fixedDeltaTime / (0.03f / Time.timeScale); if (isMove) { trBoss.position = Vector2.MoveTowards(trBoss.position, target, s * 0.5f); if (trBoss.position == (Vector3)target) { isMove = false; if (isLife) { StartCoroutine(Mover()); } } } if (isMoveSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, saw1target, s * 0.1f); if (saw1.position == (Vector3)saw1target) { isMoveSaw1 = false; if (isLife) { SawMover1(); } } } if (isMoveSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, saw2target, s * 0.1f); if (saw2.position == (Vector3)saw2target) { isMoveSaw2 = false; if (isLife) { SawMover2(); } } } if (isMoveSaw3) { saw3.position = Vector2.MoveTowards(saw3.position, saw3target, s * 0.1f); if (saw3.position == (Vector3)saw3target) { isMoveSaw3 = false; if (isLife) { SawMover3(); } } } if (isMoveLaser1) { laser1.position = Vector2.Lerp(laser1.position, laser1target, s * 0.1f); if (laser1.position == (Vector3)laser1target) { isMoveLaser1 = false; if (isLife) { LaserMover1(); } } } if (isMoveLaser2) { laser2.position = Vector2.Lerp(laser2.position, laser2target, s * 0.1f); if (laser2.position == (Vector3)laser2target) { isMoveLaser2 = false; if (isLife) { LaserMover2(); } } } if (isMoveTraps) { trap1.position = Vector2.MoveTowards(trap1.position, traptarget1, s * 0.1f); trap2.position = Vector2.MoveTowards(trap2.position, traptarget2, s * 0.1f); trap3.position = Vector2.MoveTowards(trap3.position, traptarget3, s * 0.1f); trap4.position = Vector2.MoveTowards(trap4.position, traptarget4, s * 0.1f); lr1.SetPosition(0, trap1.position); lr1.SetPosition(1, trap2.position); lr2.SetPosition(0, trap3.position); lr2.SetPosition(1, trap4.position); if (trap1.position == (Vector3)traptarget1) { isMoveTraps = false; if (isLife) { TrapMover(); } } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject == p) { if (isActive == false) { isActive = true; Start(); } if (isMove == true) { hb.StraightDamage(10f, "Boss3"); } else { health = health - ppl.power; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); trail.startColor = srBoss.color; if (health <= 0f) { isLife = false; isMove = false; saw1target = trBoss.position; saw2target = trBoss.position; saw3target = trBoss.position; isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; sr.enabled = false; cc.enabled = false; exp.SetActive(true); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); Vector2 diff = trBoss.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); int fieldBoss = bt.BoxPos(trBoss.position); Vector4 r = boxs[laser1Fields[fieldBoss]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldBoss]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); StartCoroutine(Ended()); } } } } public void EndedCoroutine() { if (!isActive) { //Debug.Log("End"); isActive = true; StartCoroutine(Ended()); } } public IEnumerator Ended() { yield return new WaitForSeconds(6.5f); if (hb.healthBarImage.fillAmount != 0f) { animatorEnd.SetBool("isActive", true); StartCoroutine(EndedFunction()); } } public IEnumerator EndedFunction() { yield return new WaitForSeconds(1.5f); if (hb.healthBarImage.fillAmount != 0f) { PlayerPrefs.SetInt("progress", 35); SceneManager.LoadSceneAsync(loadScene); } } public void ControlDamagers(bool lt, bool lm, bool tm, bool sm, bool sam) { LaserTarget.SetActive(lt); LaserMover.SetActive(lm); TrapsMover.SetActive(tm); SawMover.SetActive(sm); SawsAroundMover.SetActive(sam); } } 

音频和音乐

我也不会写音乐,但是我有足够的音乐品味来找到合适的音乐。在我的计划中,必须为每个级别选择一个曲目。在很大程度上,我实现了计划:我选择了25条曲目。我搜索了资产商店中的所有曲目。我在freesound.org或类似网站上听了其余的声音。

技术部分的声音是按照简单的原理制作的:在摄像机上有5个禁用的AudioSource和一个AudioBase脚本来控制声音。在此脚本中,SetSound的主要功能是音量,循环,类型(音乐或声音)以及音频文件本身的参数。发出信号后,声音开始播放,并且(如果未循环播放)IEnumerator以等于音轨长度的时间打开,并且在它到期后,它关闭了该组件。

脚本AudioBase
 using UnityEngine; using System.Collections; public class AudioBase : GlobalFunctions { public AudioSource[] layerSounds = new AudioSource[0]; public GameObject music; private float musicValue, soundValue; private int lengthLayerSounds = 0; private bool soundActive = true; private Coroutine offsetActive; private int lowerSoundCoroutineCounter = 100; private int upSoundCoroutineCounter = 0; public void Awake() { soundActive = PlayerPrefs.GetString("graphicsquality") != "low"; musicValue = PlayerPrefs.GetFloat("music"); soundValue = PlayerPrefs.GetFloat("sound"); lengthLayerSounds = layerSounds.Length; for (int i = 0; i < lengthLayerSounds; i++) { layerSounds[i].enabled = false; } } public void LowerSound(float timer, int upd, int id, TypePlaying typePlaying) { lowerSoundCoroutineCounter = upd; if (typePlaying == TypePlaying.Music) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(LowerSoundCoroutine(timer, upd, id, soundValue)); } } public void UpSound(float timer, int upd, int id, TypePlaying typePlaying) { upSoundCoroutineCounter = 0; if (typePlaying == TypePlaying.Music) { StartCoroutine(UpSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(UpSoundCoroutine(timer, upd, id, soundValue)); } } public IEnumerator LowerSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen - timer) * volumeSen, 0f, 1f); if (lowerSoundCoroutineCounter > 1) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, volumeSen)); lowerSoundCoroutineCounter -= 1; } } public IEnumerator UpSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen + timer) * volumeSen, 0f, 1f); if (upSoundCoroutineCounter < upd) { StartCoroutine(UpSoundCoroutine(timer, upd, id, volumeSen)); upSoundCoroutineCounter += 1; } } public void UpdateSound() { if (soundActive) { float time = Time.timeScale; for (int i = 0; i < lengthLayerSounds; i++) { AudioSource audioSource = layerSounds[i]; if (audioSource.enabled == true) { audioSource.pitch = time; } } } } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { StartCoroutine(SetSoundTime(audioClip, layerSound, volume, typePlaying, loop, time)); } public IEnumerator SetSoundTime(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { yield return new WaitForSeconds(time); SetSound(audioClip, layerSound, volume, typePlaying, loop); } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop) { if (volume == 0f) { return; } if (soundActive) { AudioSource audioSource = layerSounds[layerSound]; audioSource.enabled = true; audioSource.clip = audioClip; audioSource.loop = loop; if (typePlaying == TypePlaying.Sound) { audioSource.volume = soundValue * volume; } else { audioSource.volume = musicValue * volume; } audioSource.Play(); if (offsetActive != null) { StopCoroutine(offsetActive); offsetActive = null; } if (!loop) { offsetActive = StartCoroutine(Offet(layerSound, audioClip.length, audioSource)); } } } public IEnumerator Offet(int layerSound, float length, AudioSource audioSource) { yield return new WaitForSeconds(length); if (audioSource.clip == layerSounds[layerSound].clip) { AudioSource audioSource2 = layerSounds[layerSound]; audioSource2.Stop(); audioSource2.enabled = false; } } } 


此外,流浪汉组件(印记)具有自己的声音系统:当玩家输入印章的印章时,负责声音的组件会打开。如有必要,他可以确定产品与演奏者之间的距离,并在计算出系数后给出必要的音量,从而创造出逼真的声音效果。但这并不能按我想要的方式工作,可能是因为代码错误。

情节

是,本场比赛的故事。他具有2个特征:他几乎是非语言的,并且他的选择会影响游戏的结局。最好讲一下可变性(因为事实上,可变性是整个情节)。

游戏有3个选择:在前两个头目和第32级。与头目的选择非常明显:可以分别通过发起攻击或到达下一个级别来杀死或不杀死它们。而在第32级,情况稍微复杂一点:您可以激活触发器,这意味着唤醒本地故事保存锚(名为AI的角色)。前两个老板的​​选择会影响是否要与3个老板进行战斗。如果您杀死前两个老板中的至少一个,则将与第三个老板发生争斗。如果没有,那就没有。

只有四个结局:好,坏,中立和秘密。他们受到2个选择的影响:AI激活和杀死3个头目。我将按顺序分析结尾:

好结局
如果没有杀死3个首领并且激活了AI,则会发生这种情况。在其中发生了AI独白,他暗示继续,并显示了灼热的眼睛(来自不同的射击游戏效果)。


结束文字
谢谢啦
你能恢复我
并没有设法唤醒游侠
显然,它将会是唯一成功的情况下
,你应该休息一下了
你赢了,

结局不好
如果3个首领被杀而AI未激活,则会发生这种情况。主管的独白(玩家的“创造者”)出现在其中,并收到一丝崩解的声音,然后出现一声尖叫(非常奇怪)。

结束文字




-1

中性结局
, 3 . , , …



,





秘密结局
, 3 . , - (!) . ( , )
-
, -
,

, ,

, , , , ...

但是为什么情节几乎是非语言的?由于结尾,我无法完全将其设为非语言。但是游戏中有足够的文字。实际上,为了向玩家解释“针对游戏的ENT”,带有音符的终端出现在游戏中,并且对游戏的脚本进行了详细的解释。

场景

在这种情况下场景是世界的史前历史,从游戏的面孔和角色中以笔记,日志,报告,独白和对话的形式揭示出来:一般而言。这是一个程序员的笔调狂妄想,甚至Glukhovsky也会感到惊讶(我没有反对他,我爱Metro)。不幸的是,我没有太多时间来创建成熟的NPC。尽管我在游戏中为他们找到了精灵:


同样,由于情况的缘故,脚本是最后写的,并且已经在上面写了一个情节,据此我完成了游戏。当我上班时,他每个工作日在小巴上写了四个星期。即使在这么短的时间内,我也可以写很多东西。

如果有的话,原始游戏没有情节,也没有任何暗示。现在,隐藏情节对我来说毫无意义(毕竟,没人会完全通过游戏并阅读所有笔记)。此类游戏狂妄想的目标是三个:增加玩家动作的合理可变性,解释莫名其妙的游戏内容以及使玩家对其游戏至少感兴趣。

我以一种非常简单的方式编写了脚本:首先,我在2-3周内用40-50个句子编写了一个故事。然后,对于每个音符,我根据句子进行选择,并且已经从一个句子开始,在该音符中添加了2-3个句子,将其更改为独白(或其他旁白),并获得了现成的平衡音符。结果,从所有音符的这种接收中,总共积累了160个带有信息的句子。

您需要了解:在我的游戏中,有足够多的不合逻辑的东西,并且为了以故事的形式真实地证明每个故事的合理性,需要大量文本。因此,我尽量不要倒水,每个句子要么试图充满意义,要么试图掩盖情节的漏洞,或者试图绘画和揭示故事的特征。但是即使如此,写作水平仍然令人怀疑。

那么脚本在说什么呢?如果很简单,那就是Portal情节,只有一个开放的世界历史和稍微修改过的角色(更喜怒无常)。顺便说一句,这种情况具有一个特征:无生命物体的性别已经变得平均,尽管俄语(以及其他语言)的逻辑,常识或规则也是如此。如果某个人突然(很突然)对它感兴趣,那么我将在此处保留完整脚本和所有游戏说明:

剧本
:
, , (3 )
(1 )
, RLIS (2 )

:

[1] . [3] : , , , , .. . [3] , , , ([2] , , ). (4)

[4] , . [5] , . [6] . [6] , . [7] . (5)

[8] : . [9] (- ) . [10] ( ). (3)

[11] . [12] , , , . [13] . [13] , . (3)

[15] ([14] — , , ). [15] ( ) ([16] ). (4)

[17] . [18] «». [18] . [19]- . (4)

[20] «». [21] , . [22] , . (3)

[23] . [24] . [25] « ». (3)

[26] , . [27] , - , . [28] « ». [29] . (4)

[30] - ( ?) ( , ). [31] . [32] , ([33] , ), - , . [34] . [35] , . [36] ( ): 10 (10 = 1 ) . [X]- ( ) , ( 2 1 ?). [37] 2 . (9)

剪贴簿
():

1) {} «» , . , . - , .

2) {} RLIS (reasonable likeness in simulation) — . . RLIS ( ) — .

3) {} RLIS 100 : , , , , .. , , , . , .

4) {} , . , , , , . magnum opus .

5) {ARSotLotC} , . , . , .

6) {} -!!! - , . , , , . , , , . 2 : .

7) {} , backup , . : , . , , .

8) {} . , . , , , ( ).

9) {} , , -. ? . , . , . …

10) {} - , . . , …

11) {ARSotLotC} . , «» , , . , … .

12) {} , «» . . «», . . .

13) {} , . , . , . .

14) {} , , ( ). — , . : , .

15) {} — . , . . , , .

16) {} ? , . .

17) {} . . , «». . , , .

18) {} «». , ? , , .

19) {} «» , . , , , , . . .

20) {} '' ''. , , '' '', , .

21) {} '' : , ''. , . .

22) {ARSotLotC} : , . , .

23) {} , . ' '. , .

24) {} , , . , . , .

25) {} . , - . , , ' '.

26) {} . , . . ' ' !

27) {} ' ' , . . : , , .

28) {ARSotLotC} - < > . . . , .

29) {ARSotLotC} . ? , (- , ) ARSotLotC (Automatic Recording System of the Logs of the Complex).

30) {ARSotLotC} «» , . , , . - , backup . , , .

31) {ARSotLotC} : . , . . backup.

32) {ARSotLotC} . . , . .

33) {ARSotLotC} ( backup'). .

34) {ARSotLotC} , . , , 10 . , . Ps: , , .

35.1) {} . . ' ' . , , ''. , - , . ' '.

35.2)

代码库

由于我的专长是程序员,因此代码是我的主要任务。与原始代码库相比,续集代码库增加了2到3倍(即使原始代码库包含900行代码,因为我担心使用捆绑包,例如循环和数组或GetChild()和循环) )

随着数量的增加,代码的整体质量也有所提高,但是我无法避免出错。结果,代码本身存在很多错误。即使我客观上微薄的知识,我也能完美地看到自己的错误。因此,我们将分析我最重要的错误。以一个简单的代码为例:

 public class VelocityRotate : MonoBehaviour { public float rotate = 0f; public bool oneTime = true; private bool active = true; public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true) { active = false; } Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; } } 

您是否迅速了解了此脚本的作用?如果您这样做:

 public class VelocityRotate : MonoBehaviour { //      public float rotate = 0f;//  public bool oneTime = true;//  private bool active = true;//  public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true)//   { active = false; } //   Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle)//   { float power = Mathf.Sqrt(ax * ax + ay * ay);//  float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; //    offset' return Quaternion.Euler(0, 0, angle) * Vector2.up * power; //        } } 

缺少评论是我开发游戏的第一个也是最大的错误!在其整个代码库中,没有一个注释可以解释代码的这个或那个分支所负责的内容。也许对于小型独立游戏而言这不是必需的。好吧,首先,我绝对不能将这款游戏称为小游戏;其次,作为未来的开发人员,我肯定必须在团队中工作,而缺乏这样一种有用的习惯(如发表评论)将对我不利。我现在才意识到这个错误:它困扰着我所有与编程有关的项目,这一次,我考虑了这一点,下一次我将发表评论。

错误和缺陷

有很多错误。很好!对于如此庞大的工作,我分配了整整一个月的修改时间(八月)。解析示例是没有意义的,我只是在注释中记录了所有已记录的错误(尽管我没有记录大多数错误并就地纠正了它们):

GB2清单
:
// —
\ —

//1) , ,
//2) :
//3)
//4) TipsGamePlay
//5) ( )
//6) 0:
//7) 1: ()
//8)
//9) 2: 2
//10)
//11) 4:
//12) layer Player
//13) 7: ()
//14) 8: ( 1)
//15) 8:
\16) ( )
\17) 8: zero
//18)
//19) 1:
//20) ,
//21)
//22) timescale=0
//23) 6:
//24) 0:
//25)
//26) 7:
//27) 7:
//28) AspectRatio
\29)
//30)
//31) <EXfgpy)b> //32) 7: -
//33) ,
//34) 9:
//35) 9:
//36) 'loop'
//37) 10:
//38) 11: ()
//39) 11:
//40) 11:
//41) 11:
//42) 11:
//43) 11:
//44) ( )
/45) 12:
\46) Raycast
\47) ( static, dynamic, kinematic)
//48) (next level, next start, next end)
\49) 1: elevatorsave = 0
\50) offset angle,
//51) 2:
//52)
//53) 7:
//54) next save
//55) Dynamic Graph
//56) 11: ( )
57) 11:
//58) 9: ()
//59) 11: ( )
//60) 12: ( 2 . active , . .
61) :
//62) : -
//63) :
64)
//65) (. )
//66)
//67) HealthBar
68) 0:
//69) localposition position
70) 14: bool isPresentation
//71) 17: 2 4
72) ()
\73)
//74)
//75) layer,
//76)
//77) 2: 1
\78) ( )
//79)
//80) 3: ,
//81)
//82) 6: ,
//83) 6: 1
//84) 6:
//85) 7: 40. .
//86)
//87) 9:
//88) 32:
//89) offsetAngle elevator
//90) 11:
//91) ( )
//92)
//93)
//94)
//95) 13:
//96) 15:
/97) 3 isshotmode
//98) 17:
//99) 18: ,
//100) 19: ( )
/101) 20:
\102) Tramp
//103) 20:
\104)
//105) 11: ui
//106) text arial
\107)
//108)
//109) 3:
//110) 3:
//111) 3: ,
//112) ,
//113) ()
//114) 4:
//115) ( )
//116) ()
//117) pointsAnimation basicAnimation
//118) 7:
//119) 9:
//120) AudioBase
//121) pointsAnimation
//122) , ( )
//123) 13: HealthBar
//124) 13: ,
//125) 14: kinematic (. )
//126) 14:
//127) 14: ,
//128) velocityField ( , )
//129) 16: velocityField
//130) 22:
//131) 22:
\132) 25:
//133) 26:
//134) 27:
\135) ( )
//136)
//137) :
//138) ( )
//139)
//140) 8:
//141) ( 1.5-2, -oneshot'
\142) lerp
//143) , , ( , )
//144) 22:
//145) 11:
//146) 11:
//147) 11:
//148)
//149) «Home» «Menu»
//150)
//151)
//152)
\153) ( healthEnd)
//154) :
//155) 33: ,
//156) 15: ( 0.1)
//157) 15: velocityfield healthbar
//158)
//159) basicAnimation (27)
//160) (18, 27)
//161)
\162) 19: -
//163) ( trigger collision)
//164) 20: 50 250
//165) shotmode
//166) 27:
//167) 28:
//168) 17:
//169) tag boss3
\170) ( , )
//171) 35
//172) : , 600 «I'll come back»
//173) 33:

//174)
//175) HealthBar
//176) ( damage-
//177) 27:



0) (0)
1) (2)
2) (2)
3) (1)
4) (1)
5) (1)
6) (1)
7) (1)
8) (2)
9) (1)
10) (0)
11) (1)
(13)
12) (0)
13) (2)
14) (2)
15) (0)
16) (0)
17) (1)
18) (1)
19) (3)
20) (0)
21) (3)
22) (1)
(13)
23) (1)
24) (1)
25) (0)
26) (0)
27) (0)
28) (3)
29) (1)
30) (2)
31) (0)
32) (0)
33) (1)
34) (1)
(10)


拆卸是有意义的。并不是可以归因于错误的小错误,而是归因于游戏性能中最严重错误的大错误。我还想指出,缺陷不是缺陷。游戏有很多缺点,这是可以理解的,但是我想指出我可以解决或阻止创建的那些问题。

那我的主要缺点是什么?

  1. . , . 2 3-4 . , , : 10 . , . .
  2. , . , , , , .
  3. . , « » 60% . , .

本地化

由于场景成熟,本地化文本的数量已增长了大约30倍。但是翻译技术并没有改变:我通过Google翻译进行翻译时,我继续。只是起初我是直接从俄语翻译的,现在我将英语翻译为正确的错误,并且已经将其翻译为其他语言。另外,语言的数量也减少了:如果原始游戏有18种语言,并且其页面被翻译成Google支持的所有语言,则续集仅被转换为10种语言:游戏中的内容,页面上的内容(这是唯一的续集)不如原始)。

对于普通的笔记终端,我为处理文本制定了一个足够大的方案。简而言之,不是简单的字符串,而是一个用于处理不同语言的特殊类:

脚本StringLanguageMinimize
 [System.Serializable] public class StringLanguageMinimize { public string english = ""; public string spanish = ""; public string italian = ""; public string german = ""; public string russian = ""; public string french = ""; public string portuguese = ""; public string korean = ""; public string chinese = ""; public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } } 


与终端完全相同的类:
脚本终端
 [System.Serializable] public class StringLanguage { [TextArea] public string english = ""; [TextArea] public string spanish = ""; [TextArea] public string italian = ""; [TextArea] public string german = ""; [TextArea] public string russian = ""; [TextArea] public string french = ""; [TextArea] public string portuguese = ""; [TextArea] public string korean = ""; [TextArea] public string chinese = ""; [TextArea] public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } } 


接下来是终端触发代码:

脚本提示输入
 using UnityEngine; public class TipsInput : MonoBehaviour { public int idTips = 0; public bool isPress2Read = true; public bool oneTime = true; private bool active = true; public GameObject[] copys; private Data data; private Press2Read p2r; private TipsInput ti; private void Awake() { data = GameObject.FindWithTag("MainCamera").GetComponent<Data>(); p2r = GameObject.FindWithTag("Press2Read").GetComponent<Press2Read>(); ti = GetComponent<TipsInput>(); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { if (isPress2Read == false && active == true) { Disable(); data.SetDialoge(idTips); if (copys.Length != 0) { for (int i = 0; i < copys.Length; i++) { copys[i].GetComponent<TipsInput>().Disable(); } } } else if (isPress2Read == true) { p2r.Active(ti); } } } public void OnCollisionExit2D(Collision2D collision) { if (isPress2Read == true) { p2r.DeActive(); } } public void Disable() { if (oneTime == true) { active = false; } return; } } 


重要班级数据:

资料
 using UnityEngine; using UnityEngine.UI; using System.Collections; public class Data : GlobalFunctions { public Dialoge[] dialoges; public DeadPhrases[] deadPhrases; public GamePlay[] gameplay; [Space] public Tips tips; public AudioBase audioBase; public TipsGamePlay gamePlayTips; public Image slowmobonus; public Text fpsText; public float scaleTips = 1f; public float scaleGameUI = 1f; public float scaleSlowMo = 1f; private float speed = 0f; private float target = 1f; private float timeDuration = 1f; private int updFPS = 0; public void Awake() { scaleTips = scaleGameUI = scaleSlowMo = 1f; slowmobonus.color = new Color(0f, 0f, 0f, 0f); } public void Start() { StartCoroutine(SecFPSUpdate()); } public void SetDialoge(int id) { if (dialoges.Length != 0) { tips.SetActiveTrue(dialoges[id].dialogeStrings, dialoges[id].name); } } public void FalseP2R() { tips.SetFalse(); } public string GetDeadPhrase(string typeDead) { int idType = -1; for (int i = 0; i < deadPhrases.Length; i++) { if (deadPhrases[i].typeDead == typeDead) { idType = i; break; } } if (idType == -1) { return typeDead; } int rand = Random.Range(0, deadPhrases[idType].deadPhrases.Length); return deadPhrases[idType].deadPhrases[rand].GetString(); } public string GetDeadPhrase2() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = "Tap to continue"; break; case "spanish": ret = "Pulse para continuar"; break; case "italian": ret = "Tocca per continuare"; break; case "german": ret = "Tippen Sie, um fortzufahren"; break; case "russian": ret = "  "; break; case "french": ret = "Appuyez sur pour continuer"; break; case "portuguese": ret = "Clique para continuar"; break; case "korean": ret = "계속하려면 탭하세요"; break; case "chinese": ret = "点按即可继续"; break; case "japan": ret = "タップして続行します"; break; } return ret; } public void PauseGameUI(float time) { scaleGameUI = time; Update(); audioBase.UpdateSound(); } public void SetGamePlayTips(int id) { if (id == -1) { gamePlayTips.SetActiveTrueSaved(); } else { gamePlayTips.SetActiveTrue(gameplay[id]); } } public void SlowMo(float timeDuration2, float setSlowMo, float speed2) { speed = speed2; target = setSlowMo; timeDuration = timeDuration2; Update(); audioBase.UpdateSound(); } public void SlowMo(float timeDuration2) { scaleSlowMo = 0.1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public IEnumerator EndAnim(float timeDuration) { yield return new WaitForSeconds(timeDuration); End(); } public void End() { scaleSlowMo = 1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public void End2(float timeDuration2) { if (timeDuration2 == 0) { End(); return; } StartCoroutine(EndAnim(timeDuration2)); } private void Update() { Time.timeScale = scaleTips * scaleSlowMo * scaleGameUI; Time.fixedDeltaTime = 0.03f * scaleSlowMo * scaleTips; updFPS = updFPS + 1; return; } private IEnumerator SecFPSUpdate() { yield return new WaitForSeconds(1f); fpsText.text = "FPS: " + updFPS; updFPS = 0; StartCoroutine(SecFPSUpdate()); } } 


还有主要的Tips类,它负责终端的操作:

脚本提示
 using System.Collections; using UnityEngine.UI; using UnityEngine; public class Tips : GlobalFunctions { public Data data; public Press2Read p2r; public GameUI gameUI; public GameObject obj; public AudioClip setClip; public Text nameText; public Text txt; private int textID = 0; private int textsID = 0; private AudioBase audioBase; private DialogeString textActive; private DialogeString[] textsActive; private bool isMass = false; [TextArea] public string end = ""; [TextArea] public string endPast = ""; public void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); data.scaleTips = 1f; obj.SetActive(false); txt.text = ""; } public void SetActiveTrue(DialogeString text, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textActive = text; isMass = false; StartCoroutine(TimerFalse()); } public void SetActiveTrue(DialogeString[] texts, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textsActive = texts; isMass = true; StartCoroutine(TimersFalse()); } public IEnumerator TimerFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textActive.dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; if (textID + 1 != ds.Length && ds != end) { if (ds.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textActive.isSkip) { if (textActive.skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textActive.skipOffset); } } } } public IEnumerator TimersFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textsActive[textsID].dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; string ds1 = textsActive[textsID].dialogeString.GetString(); if (textID + 1 != ds1.Length && ds1 != end) { if (ds1.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds1.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "!") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textsActive[textsID].isSkip) { if (textsActive[textsID].skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textsActive[textsID].skipOffset); } } } } public IEnumerator IsSkip(float time) { yield return new WaitForSecondsRealtime(time); SetActiveFalse(); } public void SetFalse() { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } public void SetActiveFalse() { if (isMass == false) { if (textActive.dialogeString.GetString() != end) { end = textActive.dialogeString.GetString(); if (textActive.isSkip) { SetActiveFalse(); } } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } else { if (textsActive[textsID].dialogeString.GetString() != end) { if (textsActive[textsID].isStep == true) { txt.text = end = textsActive[textsID].dialogeString.GetString(); if (textsActive[textsID].isSkip) { SetActiveFalse(); } } else { end = textsActive[textsID].dialogeString.GetString(); txt.text = endPast + end; } } else { if (textsID != textsActive.Length - 1) { textsID = textsID + 1; textID = 0; end = ""; if (textsActive[textsID].isStep == true) { endPast = ""; } StartCoroutine(TimersFalse()); } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); p2r.UnTap(); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } } } } 


我认为如果只显示文本会令人沮丧,因此,在IEnumerator的帮助下,我模拟了编写文本的过程(最后效果完全相同)。

发布

最初,我的计划是在9月1日发布这款游戏。所以我做到了:在最后一刻,我发现我在结尾处有4个bug(并且也没有翻译),迅速修复并在晚上布置了游戏。不幸的是,支票被推迟了7天,因为我决定手动检查报价。问题很可能在帐户中,该帐户已被“定义”,并且已经通过审核手动进行了检查。

对我来说,公关要比准备发行要困难得多,因为没有金钱和联系,但我想发行游戏。因此,我使用了简单的方法:将其全部扔给VK中的朋友,在Reddit上创建帖子,将其扔入手机游戏网站的报价中,尝试与音乐作者联系,等等。结果是:


结果是

惊人的,但就在我发布这篇文章的那一天,我在IT领域度过了3年!尽管我16岁,但那天还是13岁那一天,我为自己设定了目标:学习编程并创建梦想游戏。从那一刻起,我的梦想实现了。

那游戏呢?我对她很满意。不,真的,我没有从这个项目中获得太多有用的信息和经验。好吧,游戏的质量可能显然更高,但即使是已经对我有利的游戏。另外,对我来说,这个游戏是个人化的东西,首先将这个游戏货币化是不敬的。因此,其中没有广告,捐赠且没有付费版本

在此之后,我想继续参与游戏开发。但是生活条件使这种生活不再可能。为了正常开始成为一名程序员,我需要发展,个人发展要超越我自己。我不知道现在该学什么,该去哪里,但我肯定知道一件事:这很可能是我最后一次使用统一引擎。

至少感谢您的关注。如果我的故事变得混乱,请提出问题,我会澄清。

PS:有人喜欢最后的预告片:


因此,这是此游戏的预告片:

Source: https://habr.com/ru/post/zh-CN468363/


All Articles