LuaとLÖVEでゲームを作成する-5

画像






目次





13.スキルツリー



14.コンソール



15.最終



パート12:その他の受動的スキル



ボレー



残りの攻撃を実装することから始めます。 最初は、次のようなBlast攻撃です。



GIF






ショットガンのように、いくつかの砲弾が異なる速度で発射され、その後すぐに消えます。 すべての色はnegative_colors



テーブルから取得され、各発射物は通常よりもダメージが少なくなります。 攻撃テーブルは次のようになります。



 attacks['Blast'] = {cooldown = 0.64, ammo = 6, abbreviation = 'W', color = default_color}
      
      





そして、シェルの作成プロセスは次のようになります。



 function Player:shoot() ... elseif self.attack == 'Blast' then self.ammo = self.ammo - attacks[self.attack].ammo for i = 1, 12 do local random_angle = random(-math.pi/6, math.pi/6) self.area:addGameObject('Projectile', self.x + 1.5*d*math.cos(self.r + random_angle), self.y + 1.5*d*math.sin(self.r + random_angle), table.merge({r = self.r + random_angle, attack = self.attack, v = random(500, 600)}, mods)) end camera:shake(4, 60, 0.4) end ... end
      
      





ここでは、プレイヤーが移動する方向から-30〜+30度の範囲のランダムな角度で12個のシェルを作成します。 また、500〜600の範囲で速度をランダム化します(通常、その値は200です)。つまり、発射物は通常の約3倍速くなります。



ただし、シェルをすぐに消えるようにしたいため、これでは目的の動作が得られません。 これは次のように実装できます。



 function Projectile:new(...) ... if self.attack == 'Blast' then self.damage = 75 self.color = table.random(negative_colors) self.timer:tween(random(0.4, 0.6), self, {v = 0}, 'linear', function() self:die() end) end ... end
      
      





ここで3つのことが起こります。 まず、ダメージ値を100未満に設定します。これは、100 HPで普通の敵を殺すには、1つではなく2つのシェルが必要であることを意味します。 この攻撃では12個のシェルが同時に発射されるため、これは論理的です。 次に、 negative_colors



テーブルからランダムに選択して、発射物の色を設定します。 コードのこの場所で、これを実行することに慣れています。 最後に、0.4〜0.6秒のランダムな期間の後、この発射体を破壊する必要があることを報告します。これにより、望ましい効果が得られます。 さらに、発射体を破壊するだけでなく、見た目が少し良くなるため、速度を0に下げます。



これはすべて、必要な動作を作成し、すでに完了しているようです。 ただし、記事の前の部分で多くのパッシブスキルを追加した後は、これらのパッシブスキルで後に追加するすべてのものが適切に機能するように注意する必要があります。 たとえば、前のパートの最後では、砲弾の効果を追加しました。 Blast攻撃の問題は、Blastシェルが0.4〜0.6秒で死ぬため、シールド発射体の効果と組み合わされないことです。



この問題を解決する1つの方法は、干渉するパッシブスキル(この場合はシールド)を分離し、それぞれの状況に独自のロジックを適用することです。 発射物のshield



値がtrueである状況では、発射物は他のすべてに関係なく6秒間存在するはずです。 そして、他のすべての状況では、攻撃によって指定された期間は保持されます。 これは次のようになります。



 function Projectile:new(...) ... if self.attack == 'Blast' then self.damage = 75 self.color = table.random(negative_colors) if not self.shield then self.timer:tween(random(0.4, 0.6), self, {v = 0}, 'linear', function() self:die() end) end end if self.shield then ... self.timer:after(6, function() self:die() end) end ... end
      
      





この決定はハックのように思えますが、新しい受動的スキルを追加すると徐々に複雑になり、条件を追加する必要があることは容易に想像できます。 しかし、私の経験からすると、この方法は他の誰よりも最も簡単でエラーが発生しにくい方法です。 この問題を別のより一般的な方法で解決しようとすることができますが、通常は意図しない結果になります。 おそらくこの問題に対するより良い一般的な解決策がありますが、私は個人的には考えていませんでしたが、それを見つけられなかった場合、次の最良の解決策は最も単純なもの、すなわち、何ができて何ができないかを決定する多くの条件付き構成になります。 そうであっても、 if not self.shield



は、発射物の寿命を変更する新たな攻撃が追加さif not self.shield



たびに条件に先行します。



172.(CONTENT)受動的スキルprojectile_duration_multiplier



実装します。 期間に関連するすべてのProjectileクラスの動作に使用することを忘れないでください。



回転



次の実行可能な攻撃はスピンです。 次のようになります。



GIF






これらのシェルは、一定の値で常に角度を変更します。 これを実現するには、角度の変化率を示す変数rv



を追加し、この値を各フレームのr



に追加します。



 function Projectile:new(...) ... self.rv = table.random({random(-2*math.pi, -math.pi), random(math.pi, 2*math.pi)}) end function Projectile:update(dt) ... if self.attack == 'Spin' then self.r = self.r + self.rv*dt end ... end
      
      





絶対値がmath.pi未満または2 * math.piを超えないようにするため、-2 * math.piから-math.piまでの間隔またはmath.piから2 * math.piまでの間隔を選択します。 。 絶対値が低いと、発射物によって作られる円が大きくなり、絶対値が大きいと、円が小さくなることを意味します。 円のサイズを、見た目を良くするために必要な値に制限します。 また、負の値と正の値の違いは、円が回転する方向にあることを理解する必要があります。



さらに、Spinシェルを永久に存在させたくないので、Spinシェルに寿命を追加できます。



 function Projectile:new(...) ... if self.attack == 'Spin' then self.timer:after(random(2.4, 3.2), function() self:die() end) end end
      
      





shoot



機能は次のようになります。



 function Player:shoot() ... elseif self.attack == 'Spin' then self.ammo = self.ammo - attacks[self.attack].ammo self.area:addGameObject('Projectile', self.x + 1.5*d*math.cos(self.r), self.y + 1.5*d*math.sin(self.r), table.merge({r = self.r, attack = self.attack}, mods)) end end
      
      





そして、攻撃テーブルは次のようになります。



 attacks['Spin'] = {cooldown = 0.32, ammo = 2, abbreviation = 'Sp', color = hp_color}
      
      





このようにして、必要な動作を取得します。 ただし、もう1つ、シェルからのトレースが必要です。 プレイヤーの船に使用されたものと同じトレイルを使用するホーミング発射体とは異なり、この発射体トレイルは発射体の形状と色を繰り返しますが、完全に消えるまでゆっくりと見えなくなります。 別のトレースオブジェクトに対して行ったのと同じ方法でこれを実装できますが、これらの違いを考慮します。



 ProjectileTrail = GameObject:extend() function ProjectileTrail:new(area, x, y, opts) ProjectileTrail.super.new(self, area, x, y, opts) self.alpha = 128 self.timer:tween(random(0.1, 0.3), self, {alpha = 0}, 'in-out-cubic', function() self.dead = true end) end function ProjectileTrail:update(dt) ProjectileTrail.super.update(self, dt) end function ProjectileTrail:draw() pushRotate(self.x, self.y, self.r) local r, g, b = unpack(self.color) love.graphics.setColor(r, g, b, self.alpha) love.graphics.setLineWidth(2) love.graphics.line(self.x - 2*self.s, self.y, self.x + 2*self.s, self.y) love.graphics.setLineWidth(1) love.graphics.setColor(255, 255, 255, 255) love.graphics.pop() end function ProjectileTrail:destroy() ProjectileTrail.super.destroy(self) end
      
      





それはかなり標準的なように見えますが、唯一の顕著な側面はalpha



変数を持っていることです。これはトゥイーンを介して0に変更されるため、発射物は0.1から0.3秒のランダムな時間の後にゆっくりと消え、まったく同じ方法でトレースを描画しますシェルの描き方。 親の発射物s



変数r



s



およびcolor



を使用することが重要です。つまり、それを作成するとき、それらすべてを転送する必要があります。



 function Projectile:new(...) ... if self.attack == 'Spin' then self.rv = table.random({random(-2*math.pi, -math.pi), random(math.pi, 2*math.pi)}) self.timer:after(random(2.4, 3.2), function() self:die() end) self.timer:every(0.05, function() self.area:addGameObject('ProjectileTrail', self.x, self.y, {r = Vector(self.collider:getLinearVelocity()):angle(), color = self.color, s = self.s}) end) end ... end
      
      





このようにして、必要な結果を達成します。



173.(CONTENT) Flame



攻撃を実装します。 攻撃テーブルは次のようになります。



 attacks['Flame'] = {cooldown = 0.048, ammo = 0.4, abbreviation = 'F', color = skill_point_color}
      
      





そして、攻撃自体は次のとおりです。



GIF






シェルは、0.6から1秒のランダムな時間間隔で生存し、Blastシェルに似ている必要があり、その間に速度を0に変更する必要があります。これらのシェルも、SpinシェルのようにProjectileTrailオブジェクトを使用します。 Flameの各シェルは、50ユニットのダメージを軽減します。



弾むシェル



バウンスシェルは壁で跳ね返る必要があり、壁によって破壊されることはありません。 デフォルトでは、バウンスシェルは壁に4回跳ね返り、次に壁にぶつかったときに破壊されます。 これは、 shoot



機能のopts



テーブルを使用して設定できます。



 function Player:shoot() ... elseif self.attack == 'Bounce' then self.ammo = self.ammo - attacks[self.attack].ammo self.area:addGameObject('Projectile', self.x + 1.5*d*math.cos(self.r), self.y + 1.5*d*math.sin(self.r), table.merge({r = self.r, attack = self.attack, bounce = 4}, mods)) end end
      
      





したがって、 bounce



変数には、発射物によって残されたバウンスの数が含まれます。 壁に当たるたびに1ずつ減少して使用できます。



 function Projectile:update(dt) ... -- Collision if self.bounce and self.bounce > 0 then if self.x < 0 then self.r = math.pi - self.r self.bounce = self.bounce - 1 end if self.y < 0 then self.r = 2*math.pi - self.r self.bounce = self.bounce - 1 end if self.x > gw then self.r = math.pi - self.r self.bounce = self.bounce - 1 end if self.y > gh then self.r = 2*math.pi - self.r self.bounce = self.bounce - 1 end else if self.x < 0 then self:die() end if self.y < 0 then self:die() end if self.x > gw then self:die() end if self.y > gh then self:die() end end ... end
      
      





ここでは、残りの跳ね返りの数を減らすことに加えて、当たった壁を考慮して発射体の方向を変更します。 おそらくもっと一般的な方法がありますが、各壁との衝突を個別に考慮した解決策を考え出すことができました。その後、発射体の角度を正しく反映/ミラーするために必要な計算が実行されます。 bounce



が0の場合、最初の条件構造がスキップされ、通常のパスに移動するため、発射物が破壊されることに注意してください。



setLinearVelocity



呼び出す前にこの衝突コードをすべて配置することも重要setLinearVelocity



。そうしないと、1フレームの遅延で発射物を回転させるため、バウンスは機能しませんが、単に角度を反転しても元に戻りません。 安全のために、発射体の角度を回転させることに加えて、 setPosition



を使用してその位置を強制することもできますが、私には必要ないようです。



バウンドする発射物の色は、Spread発射物と同様に、 default_colors



テーブルから取得されることを除いて、ランダムになります。 これは、 Projectile:draw



関数を個別に処理する必要があることを意味します。



 function Projectile:draw() ... if self.attack == 'Bounce' then love.graphics.setColor(table.random(default_colors)) end ... end
      
      





攻撃テーブルは次のとおりです。



 attacks['Bounce'] = {cooldown = 0.32, ammo = 4, abbreviation = 'Bn', color = default_color}
      
      





これはすべて次のようになります。



GIF






174.(CONTENT) 2Split攻撃を実装します。 これは次のようなものです。



GIF






それはHomingシェルのように見え、色ammo_color



のみを使用します。



発射物が敵に当たると、元の発射物の方向から+ -45度の角度で2つに分割されます(2つの新しい発射物が作成されます)。 シェルが壁にぶつかると、壁からの反射角で(つまり、シェルが上壁にぶつかると、2つのシェルがmath.pi / 4および3 * math.pi / 4に向けて)作成されます。発射物の反射、あなたは自分で選ぶことができます。 この攻撃のテーブルは次のようになります。



 attacks['2Split'] = {cooldown = 0.32, ammo = 3, abbreviation = '2S', color = ammo_color}
      
      





175.(CONTENT) 4Split攻撃を実装します。 これは次のようなものです。



GIF






2Split攻撃と同じように動作しますが、2つではなく4つのシェルを作成します。 シェルは、中心から45度のすべての角度で送信されます。つまり、math.pi / 4、3 * math.pi / 4、-math.pi / 4および-3 * math.pi / 4です。 攻撃テーブルは次のようになります。



 attacks['4Split'] = {cooldown = 0.4, ammo = 4, abbreviation = '4S', color = boost_color}
      
      





稲妻



Lightning攻撃は次のようになります。



GIF






プレイヤーが敵から一定の距離に達すると、稲妻が作成され、敵にダメージを与えます。 ここでの作業のほとんどは、雷を発生させることであるため、まずそれを検討します。 LightningLine



オブジェクトを作成して実装しますLightningLine



オブジェクトは、雷の電荷を視覚的に表現します。



 LightningLine = GameObject:extend() function LightningLine:new(area, x, y, opts) LightningLine.super.new(self, area, x, y, opts) ... self:generate() end function LightningLine:update(dt) LightningLine.super.update(self, dt) end -- Generates lines and populates the self.lines table with them function LightningLine:generate() end function LightningLine:draw() end function LightningLine:destroy() LightningLine.super.destroy(self) end
      
      





描画機能に焦点を当て、稲妻の作成はあなたにお任せします! このチュートリアルでは、生成方法について詳しく説明しているため、ここでは繰り返しません。 雷の電荷を構成するすべての行がself.lines



テーブルにあり、各行がキーx1, y1, x2, y2



を含むテーブルであるとself.lines



ます。 これを念頭に置いて、次のような最も簡単な方法で雷の電荷を引き出すことができます。



 function LightningLine:draw() for i, line in ipairs(self.lines) do love.graphics.line(line.x1, line.y1, line.x2, line.y2) end end
      
      





ただし、これは単純すぎます。 したがって、まずこれらの線を色boost_color



で線の太さ2.5で描画し、その上にdefault_color



と線の太さ1.5で同じ線を再度描画する必要があります。 これにより、ジッパーの充電が少し厚くなり、ジッパーのようになります。



 function LightningLine:draw() for i, line in ipairs(self.lines) do local r, g, b = unpack(boost_color) love.graphics.setColor(r, g, b, self.alpha) love.graphics.setLineWidth(2.5) love.graphics.line(line.x1, line.y1, line.x2, line.y2) local r, g, b = unpack(default_color) love.graphics.setColor(r, g, b, self.alpha) love.graphics.setLineWidth(1.5) love.graphics.line(line.x1, line.y1, line.x2, line.y2) end love.graphics.setLineWidth(1) love.graphics.setColor(255, 255, 255, 255) end
      
      





さらに、ここではalpha



属性を使用します。これは最初は255であり、ラインの存続​​期間、つまり約0.15秒でトゥイーンによって0に減少します。



それでは、このLightningLineオブジェクトの作成に移りましょう。 この攻撃は次のように動作します。プレイヤーが視界内で敵に十分近づくと、攻撃がトリガーされ、敵にダメージを与えます。 最初にすべての敵をプレイヤーに近づけましょう。 これは、特定の半径のターゲットを拾ったホーミング発射体の場合と同じ方法で行うことができます。 ただし、プレーヤーの背後にいる敵にダメージを与えてはならないため、半径をプレーヤーの中心にしないようにします。したがって、この円の中心をプレーヤーの動きの方向に前方に移動し、その後アクションを実行します。



 function Player:shoot() ... elseif self.attack == 'Lightning' then local x1, y1 = self.x + d*math.cos(self.r), self.y + d*math.sin(self.r) local cx, cy = x1 + 24*math.cos(self.r), y1 + 24*math.sin(self.r) ... end
      
      





ここでx1, y1



、つまり一般に砲弾を発射する位置(船の船首)を決定し、次にcx, cy



、つまり最も近い敵を見つけるために使用する半径の中心も決定します。 サークルを24単位シフトしています。これは、プレーヤーの背後にいる敵を選択できないように十分な大きさです。



次にできることは、ホーミングシェルでターゲットを見つけたいときにProjectileオブジェクトで使用したコードを単純にコピーアンドペーストしますが、ニーズに合わせて変更し、円の位置を円の中心cx, cy



に置き換えます。



 function Player:shoot() ... elseif self.attack == 'Lightning' then ... -- Find closest enemy local nearby_enemies = self.area:getAllGameObjectsThat(function(e) for _, enemy in ipairs(enemies) do if e:is(_G[enemy]) and (distance(ex, ey, cx, cy) < 64) then return true end end end) ... end
      
      





その後、プレイヤーの24ユニット前にある円の半径64ユニット内の敵のリストを取得します。 ここでは、敵をランダムに選択するか、最も近くにいることができます。 後者のオプションに焦点を当てます。つまり、各敵から円までの距離に基づいてテーブルをソートする必要があります。



 function Player:shoot() ... elseif self.attack == 'Lightning' then ... table.sort(nearby_enemies, function(a, b) return distance(ax, ay, cx, cy) < distance(bx, by, cx, cy) end) local closest_enemy = nearby_enemies[1] ... end
      
      





この目的のために、 table.sort



table.sort



で使用できtable.sort



。 次に、ソートされたテーブルの最初の要素を取得して攻撃するだけです。



 function Player:shoot() ... elseif self.attack == 'Lightning' then ... -- Attack closest enemy if closest_enemy then self.ammo = self.ammo - attacks[self.attack].ammo closest_enemy:hit() local x2, y2 = closest_enemy.x, closest_enemy.y self.area:addGameObject('LightningLine', 0, 0, {x1 = x1, y1 = y1, x2 = x2, y2 = y2}) for i = 1, love.math.random(4, 8) do self.area:addGameObject('ExplodeParticle', x1, y1, {color = table.random({default_color, boost_color})}) end for i = 1, love.math.random(4, 8) do self.area:addGameObject('ExplodeParticle', x2, y2, {color = table.random({default_color, boost_color})}) end end end end
      
      





最初に、 closest_enemy



がnilに等しくないことを確認する必要がありますclosest_enemy



等しい場合は、何もしないでください。 近くに敵がいないため、ほとんどの場合はゼロになります。 そうでない場合は、他のすべての攻撃と同様に弾薬を削減し、損傷している敵に対してhit



関数を呼び出します。 その後、変数x1, y1, x2, y2



でLightningLineオブジェクトを作成します。これは、敵の中心と同様に、電荷が解放される船の正面の位置を表します。 最後に、攻撃をより面白くするために、ExplodeParticleパーティクルの束を作成します。



攻撃が機能するために最後に必要なのはそのテーブルです:



 attacks['Lightning'] = {cooldown = 0.2, ammo = 8, abbreviation = 'Li', color = default_color}
      
      





これはすべて次のようになります。



GIF






176.(CONTENT) Explode



攻撃を実装します。 これは次のようなものです。



GIF






特定の半径内のすべての敵を破壊する爆発が作成されます。 シェル自体は、 hp_color



がわずかに大きいことを除いて、ホーミングシェルのように見えます。 攻撃テーブルは次のようになります。



 attacks['Explode'] = {cooldown = 0.6, ammo = 4, abbreviation = 'E', color = hp_color}
      
      





177.(コンテンツ) Laser



攻撃を実装します。 これは次のようなものです。



GIF






それを横切るすべての敵を破壊する巨大なラインが作成されます。 線または衝突を検出するための回転長方形としてプログラムできます。 ラインを使用することにした場合は、互いにわずかに離れた3行または5行を使用することをお勧めします。そうしないと、プレイヤーは時々敵を逃し、不公平に思えます。



攻撃自体の効果は他の人とは異なりますが、問題はないはずです。 太さはトゥイーンで時間とともに変化する中央の1本の大きな白い線と、最初は白い線に近いが、効果が終了すると拡大して消える2本の赤い線が側面にあります。 射撃効果は、元のShootEffectオブジェクトを拡大したもので、赤色もあります。 攻撃テーブルは次のようになります。



 attacks['Laser'] = {cooldown = 0.8, ammo = 6, abbreviation = 'La', color = hp_color}
      
      





178.(CONTENT)パッシブスキルadditional_lightning_bolt



実装します。 当てはまる場合、プレイヤーは2本の稲妻で同時に攻撃できます。 プログラミングの観点から見ると、これは、最も近い敵を1つ探す代わりに、2人を探し、存在する場合は両方を攻撃することを意味します。 また、0.5秒などの短い間隔で各攻撃を分離しようとすることもできます。



179.(CONTENT)パッシブスキルincreased_lightning_angle



た光の角度を実装します。 このスキルは、稲妻攻撃がトリガーできる角度を増加させます。つまり、側面、時にはプレイヤーの背後の敵も攻撃します。 プログラミングの観点からは、 increased_lightning_angle



がtrueの場合、稲妻円を24単位ずらさず、計算でプレイヤーの中心を使用しないことを意味します。



180.(CONTENT)受動的スキルarea_multiplier



実装します。 このスキルは、エリアに関連するすべての攻撃と効果のエリアを増加させます。 最新の例は、Lightning攻撃の稲妻円とExplode攻撃エリアです。ただし、これは爆発に一般的に適用されるだけでなく、エリアに関連するすべてのものにも適用されます(情報の取得または効果の適用に円が使用される場合)。



181.(CONTENT)受動的スキルを実装しlaser_width_multiplier



ます。このスキルは、レーザー攻撃の厚さを増減します。



182.(CONTENT)受動的スキルを実装しadditional_bounce_projectiles



ます。このスキルは、バウンスの発射物の跳ね返りの回数を増減します。デフォルトでは、バウンス攻撃シェルは4回バウンスできます。additional_bounce_projectiles



4に等しい場合、バウンス攻撃シェルは8回バウンスできます。



183.(CONTENT)受動的スキルを実装するfixed_spin_attack_direction



。ブールのようなこのスキルは、すべてのスピン攻撃シェルを一定の方向に回転させます。つまり、それらはすべて左または右にのみ回転します。



184.(CONTENT)受動的スキルを実装しsplit_projectiles_split_chance



ます。これは、すでに攻撃によって分割された2Splitまたは4Splitの発射物によって再び分割される確率を追加する発射物です。たとえば、この確率が100に等しくなると、分割されたシェルは常に再帰的に分割されます(ただし、スキルツリーではこれを許可しません)。



185.(コンテンツ)受動的スキルを実装[attack]_spawn_chance_multiplier



する[attack]



各攻撃の名前です。これらのスキルは、特定の攻撃の可能性を高めます。これで、攻撃リソースを作成すると、攻撃がランダムに選択されます。ただし、今ではチャンスリストからそれらを選択する必要があります。最初はすべての攻撃に対して同等の確率を持ち[attack]_spawn_chance_multiplier



ますが、パッシブスキルによって変化します



186(コンテンツ)パッシブスキルを実装しstart_with_[attack]



、どこ[attack]



-各攻撃の名前。これらの受動的スキルにより、プレーヤーは適切な攻撃でゲームを開始できます。たとえば、start_with_bounce



trueの場合、プレーヤーは各ラウンドをバウンス攻撃で開始します。複数の受動的スキルの値がtrueの場合start_with_[attack]



、そのうちの1つがランダムに選択されます。



追加のホーミングシェル



Passive Skill additional_homing_projectiles



は、「Homing Projectile Launch」などのパッシブスキルに追加の発射物を追加します。通常、起動されたホーミングシェルは次のようになります。



 function Player:onAmmoPickup() if self.chances.launch_homing_projectile_on_ammo_pickup_chance:next() then local d = 1.2*self.w self.area:addGameObject('Projectile', self.x + d*math.cos(self.r), self.y + d*math.sin(self.r), {r = self.r, attack = 'Homing'}) self.area:addGameObject('InfoText', self.x, self.y, {text = 'Homing Projectile!'}) end end
      
      





additional_homing_projectiles



追加のシェルをいくつ使用する必要があるかを示す数値です。これが機能するためには、同様のことができます:



 function Player:onAmmoPickup() if self.chances.launch_homing_projectile_on_ammo_pickup_chance:next() then local d = 1.2*self.w for i = 1, 1+self.additional_homing_projectiles do self.area:addGameObject('Projectile', self.x + d*math.cos(self.r), self.y + d*math.sin(self.r), {r = self.r, attack = 'Homing'}) end self.area:addGameObject('InfoText', self.x, self.y, {text = 'Homing Projectile!'}) end end
      
      





そして、launch_homing_projectile



あらゆるタイプの受動的スキルが現れる各インスタンスにこれを適用するだけです



187.(CONTENT)受動的スキルを実装しadditional_barrage_projectiles



ます。



188.(CONTENT)受動的スキルを実装しbarrage_nova



ます。これはブール変数であり、設定されると、プレーヤーの移動方向ではなく、ラインのシェルが円形に発射されるようにします。これは次のようなものです。



GIF






マインシェル



機雷弾は、その作成場所にとどまり、時間の経過とともに爆発する弾薬です。 これは次のようなものです。



GIF






ご覧のとおり、スピン攻撃の発射体のように回転しますが、はるかに高速です。これを実装するmine



ために、発射物の属性の値がtrueである場合、スピンシェルのように動作しますが、回転速度は増加します。



 function Projectile:new(...) ... if self.mine then self.rv = table.random({random(-12*math.pi, -10*math.pi), random(10*math.pi, 12*math.pi)}) self.timer:after(random(8, 12), function() -- Explosion end) end ... end function Projectile:update(dt) ... -- Spin or Mine if self.attack == 'Spin' or self.mine then self.r = self.r + self.rv*dt end ... end
      
      





ここでは、math.piから2 * math.piの絶対値の範囲で回転速度を制限する代わりに、10 * math.piから12 * math.piの絶対値を取ります。その結果、発射体ははるかに速く回転し、より小さな領域をカバーします。これは、このタイプの動作に最適です。



さらに、8〜12秒のランダムな期間の後、発射体が爆発します。この爆発は、爆発シェル用に処理された爆発と同じ方法で処理する必要はありません。私の場合、オブジェクトを作成しましたがExplosion



、このアクションを実装する方法はたくさんあります。 Explode攻撃も演習であったため、演習のままにします。



189.(コンテンツ)受動的スキルを実装するdrop_mines_chance



、0.5秒ごとにラウンドマインを離れる確率をプレイヤーに追加します。プログラミングの観点からは、これは0.5秒ごとに開始されるタイマーを介して実装されます。これらの各実行で、関数cubesをロールしますdrop_mines_chance:next()







190.(CONTENT)projectiles_explode_on_expiration



シェルが寿命のために破壊されたときに爆発することを可能にするパッシブスキル実装します。これは、彼らの人生が終わる時間にのみ適用されるべきです。発射物が敵または壁と衝突した場合、このスキルが真であるときに爆発しないはずです。



191.(CONTENT)受動的スキルを実装しself_explode_on_cycle_chance



ます。このスキルは、プレイヤーに各サイクルで自分の周りに爆発を引き起こす機会を与えます。次のようになります。



GIF






Explode攻撃と同じ爆発を使用します。作成した爆発の数、場所、およびサイズは、自分で選択できます。



192.(CONTENT)受動的スキルを実装しprojectiles_explosions



ます。それが当てはまる場合、プレイヤーによって作成された発射物から発生するすべての爆発は、パッシブスキルに似た複数のシェルを作成しますbarrage_nova



作成される発射物の数は最初5つで、この量は受動的なスキルの影響を受けadditional_barrage_projectiles



ます。



エネルギーシールド



パッシブスキルenergy_shield



が真である場合、プレイヤーのHPはエネルギーシールドに変わります(以降ESと呼ばれます)。ESのパフォーマンスは、次の点でHPのパフォーマンスと異なります。





主に関数でこれらすべてを実装できますhit







 function Player:new(...) ... -- ES self.energy_shield_recharge_cooldown = 2 self.energy_shield_recharge_amount = 1 -- Booleans self.energy_shield = true ... end function Player:hit(damage) ... if self.energy_shield then damage = damage*2 self.timer:after('es_cooldown', self.energy_shield_recharge_cooldown, function() self.timer:every('es_amount', 0.25, function() self:addHP(self.energy_shield_recharge_amount) end) end) end ... end
      
      





ヒット後にESのリロードを開始する前の一時停止は2秒であり、リロード速度は1秒あたり4 ES(コールで0.25秒に1回every



であると発表しますまた、ヒット関数の上部に条件構造を配置し、ダメージ変数を2倍にします。これは、プレーヤーにダメージを与えるために以下で使用されます。



ここで私たちに残された唯一のことは、不死身の時間を半分にすることです。ヒット関数または関数のいずれかでこれを行うことができますsetStats



この機能を長い間扱っていないため、2番目のオプションを選択します。



 function Player:setStats() ... if self.energy_shield then self.invulnerability_time_multiplier = self.invulnerability_time_multiplier/2 end end
      
      





以来setStats



その後、コンストラクタ関数の最後の呼び出しと呼び出しtreeToPlayer



(、それは木のすべてのパッシブスキルをロードした後に呼び出されている)、私たちは価値があることを確認することができますenergy_shield



スキルを反映して、ツリー内のプレイヤーを選択してください。さらに、ツリーからこの乗数のすべての増加/減少を適用した後、確実に不死身タイマーを減らすことができます。ここでは順序が重要ではないため、これは実際にはこの受動的スキルには必要ありませんが、他のスキルには重要な場合がありsetStats



ます。通常、パラメータの確率がブール変数から取得され、この変化がゲーム内で一定である場合、それをに入れる方が論理的setStats



です。



193.(コンテンツ) HP UIを変更して、いつenergy_shield



本当、それはこのように見えました:



GIF






194.(コンテンツ)energy_shield_recharge_amount_multiplier



1秒あたりに復元されるESの数を増減するパッシブスキル実装します。



195.(CONTENT)energy_shield_recharge_cooldown_multiplier



プレーヤーにダメージを与えた後、一時停止時間を増減するパッシブスキル実装します。その後、ESがリチャージを開始します。



すべてのキルイベントに信頼性を追加する



たとえば、added_chance_to_all_on_kill_events



5に等しい場合、「kill」タイプのすべてのパッシブスキルの確率は5%増加します。これは、プレイヤーが最初に合計launch_homing_projectile_on_kill_chance



8 に増加したスキルを獲得した場合、8%ではなく最終確率が13%になることを意味します。これはあまりにも強力な受動的スキルですが、実装の観点から検討することは興味深いです。



関数generateChances



リストchanceListを生成する方法を変更することで実装できます。この関数は、名前がで終わるすべてのパッシブスキルをバイパスするため_chance



、名前に含まれるすべてのパッシブスキルも解析できることは明らかです_on_kill



。つまり、これを実行した後added_chance_to_all_on_kill_events



、生成プロセスの適切な場所にchanceListを追加するだけで十分です



まず、タイトルにあるスキルと通常のパッシブスキルを分けますon_kill







 function Player:generateChances() self.chances = {} for k, v in pairs(self) do if k:find('_chance') and type(v) == 'number' then if k:find('_on_kill') and v > 0 then else self.chances[k] = chanceList({true, math.ceil(v)}, {false, 100-math.ceil(v)}) end end end end
      
      





パッシブスキルの検索に使用したのと同じ方法を使用します_chance



この行をに置き換えてください_on_kill



さらに、この受動的スキルが0%を超えるイベントを生成する確率があることも確認する必要があります。プレイヤーがこのイベントにポイントを費やしていないとき、新しいパッシブスキルが「殺されたとき」などのすべてのイベントにその確率を追加したくないので、プレイヤーがすでにある程度の確率を投資したイベントに対してのみこれを行います。



これでchanceListを作成できますが、v



自分で使用する代わりに、次のように使用しますv+added_chance_to_all_on_kill_events







 function Player:generateChances() self.chances = {} for k, v in pairs(self) do if k:find('_chance') and type(v) == 'number' then if k:find('_on_kill') and v > 0 then self.chances[k] = chanceList( {true, math.ceil(v+self.added_chance_to_all_on_kill_events)}, {false, 100-math.ceil(v+self.added_chance_to_all_on_kill_events)}) else self.chances[k] = chanceList({true, math.ceil(v)}, {false, 100-math.ceil(v)}) end end end end
      
      





弾薬を追加してASPDを増加



このスキルは、あるパラメーターの一部を別のパラメーターに変換することです。この場合、弾薬リソースのすべての増加を取得し、追加の攻撃速度として追加します。次の式でこれを実装できます。



 local ammo_increases = self.max_ammo - 100 local ammo_to_aspd = 30 aspd_multiplier:increase((ammo_to_aspd/100)*ammo_increases)
      
      





たとえば、最大備蓄量が130弾薬でammo_to_aspd



、変換率が30%の場合、結果として攻撃速度が0.3 * 30 = 9%増加します。最大弾薬が250弾の場合、同じコンバージョン率で、1.5 * 30 = 45%になります。



これを実装するには、最初に属性を定義します。



 function Player:new(...) ... -- Conversions self.ammo_to_aspd = 0 end
      
      





そして、変数に変換を適用できますaspd_multiplier



この変数はなのでStat



、関数でこれを行う必要がありますupdate



この変数が普通であれば、関数でそれを行いますsetStats







 function Player:update(dt) ... -- Conversions if self.ammo_to_aspd > 0 then self.aspd_multiplier:increase((self.ammo_to_aspd/100)*(self.max_ammo - 100)) end self.aspd_multiplier:update(dt) ... end
      
      





そして、それは意図したとおりに機能するはずです。



最後の受動的スキル



約20の受動的スキルを実現するだけです。それらのほとんどは非常に簡単なので、演習に任せます。実際、これらは以前に実装したほとんどのスキルとはほとんど完全に無関係であるため、些細なことかもしれませんが、実際に何が起こっているのか、どのように理解しているのかを確認できるタスクと考えることができますコードベースで。



196.(CONTENT)change_attack_periodically



プレイヤーの攻撃を10秒ごとに変更するパッシブスキル実装します。新しい攻撃がランダムに選択されます。



197.(CONTENT)gain_sp_on_death



死後のプレイヤーに20 SPを与えるパッシブスキル実装します。



198.(コンテンツ)受動的スキルを実装するconvert_hp_to_sp_if_hp_full



、プレーヤーにHPリソースを取得するたびに3 SPを提供します。プレーヤーのHPはすでに最大になっています。



199.(CONTENT)mvspd_to_aspd



攻撃速度に移動速度の増加を追加するパッシブスキル実装します。この増加は、に使用されるものと同じ式を使用して追加する必要がありますammo_to_aspd



。つまり、プレーヤーのMVSPDが30%増加し、30にmvspd_to_aspd



等しい(つまり、変換係数が30%である)場合、ASPDは9%増加します。



200.(CONTENT)mvspd_to_hp



プレイヤーのHPの速度を低下させるパッシブスキル実装します。たとえば、MVSPDが30%削減されmvspd_to_hp



たが、30に等しい場合(つまり、変換係数は30%)、21 HPを追加する必要があります。



201.(コンテンツ)mvspd_to_pspd



発射体の速度に速度の増加を追加する受動的なスキル実装します。まったく同じように動作しmvspd_to_aspd



ます。



202.(CONTENT)no_boost



プレイヤーのブーストアクセラレーションを無効にするパッシブスキル実装します(max_boost = 0)。



203.(CONTENT)half_ammo



プレイヤーの弾薬を半分にするパッシブスキル実装します。



204.(コンテンツ)half_hp



プレイヤーのHPを半分にするパッシブスキル実装します。



205.(コンテンツ)deals_damage_while_invulnerable



プレイヤーが不死身の場合(たとえば、ヒット後に属性invincible



がtrue 設定された場合)、接触時に敵にダメージを与えることができるパッシブスキル実装します



206.(コンテンツ)refill_ammo_if_hp_full



プレーヤーがフルHPスケールのHPリソースを選択した場合、プレーヤーの弾薬を完全に復元するパッシブスキル実装します。



207.(コンテンツ)refill_boost_if_hp_full



プレイヤーがフルHPでHPリソースを選択したときに、プレイヤーのブーストを完全に復元するパッシブスキル実装します。



208.(コンテンツ)only_spawn_boost



Boostを作成する唯一のリソースにするパッシブスキル実装します。



209.(CONTENT)only_spawn_attack



一定の時間内に攻撃以外のリソースが作成されないようにする受動的なスキル実装します。つまり、すべての攻撃は、リソースの一時停止と攻撃の一時停止(つまり、16秒ごと、および30秒ごと)の後に作成されます。



210.(コンテンツ)no_ammo_drop



弾薬が敵から落ちない受動的なスキル実装します。



211.(コンテンツ)infinite_ammo



プレイヤーの攻撃が弾薬を消費しないパッシブスキル実装します。



そして、ここで私たちは受動的なスキルで終わります。これらのスキルの多くは単なるパラメーターの変更であり、1箇所に集中するのではなくツリー全体に分散できるため、合計で約150の異なるパッシブスキルを検討しました。これらのスキルはスキルツリーで約900ノードに拡張されます。



しかし、ツリー(記事の次の部分で説明します)に進む前に、コンテンツを少し拡張し、すべてのプレイヤーの船とすべての敵を認識することもできます。あなたは私の例に完全に従わないかもしれません。私はそれを演習で行い、あなた自身の船/敵を作ります。





212.(CONTENT)敵を実現しBigRock



ます。この敵はRock



死後に4つのオブジェクトにさらに分割されるのとまったく同じように振る舞いますRock



デフォルトでは、HPは300です。



GIF






213.(CONTENT)敵を実現しWaver



ます。この敵は波の発射体のように振る舞い、時々前後に砲弾を発射します(バック攻撃のように)。デフォルトでは、70 HPです。



GIF






214.(CONTENT)敵を実現しSeeker



ます。この敵は弾薬オブジェクトのように動作し、ゆっくりとプレイヤーに向かって移動します。定期的に、この敵は地雷と同じように地雷を置きます。デフォルトでは、200 HPです。



GIF






215.(CONTENT)敵を実行しOrbitter



ます。この敵はロックやビッグロックのように振る舞いますが、彼の周りには貝殻の盾があります。これらのシェルは、前の記事で実装したシールドシェルのように動作します。Orbitterがシェルの死の前に死んだ場合、残りのシェルは短時間プレイヤーに向けられます。デフォルトでは、450 HPです。



GIF








チュートリアルの前の部分のいずれかで、すべての船のグラフィックを既に検討しました。これは演習でも実装されました。そのため、すでに名前を作成していること、および名前やその他すべてのものがあることを前提としています。次の演習では、作成したものを使用しますが、記事の前の部分と現在の部分で使用されているほとんどのパッシブスキルを実装しているため、好みに合わせて独自の船を作成できます。私自身が発明したものの例を挙げます。



216.(コンテンツ)船の実装Crusader







GIF






次のオプションがあります。





217.(CONTENT)船を実装しRogue



ます:



GIF






次のオプションがあります。





218.(CONTENT)船を実装しBit Hunter



ます:



GIF






次のオプションがあります。





219.(CONTENT)船を実装しSentinel



ます:



GIF






次のオプションがあります。





220.(コンテンツ)船を実装するStriker







GIF






次のオプションがあります。





221.(コンテンツ)船の実装Nuclear







GIF






次のオプションがあります。





222.(コンテンツ)船の実装Cycler







GIF






次のオプションがあります。





223.(CONTENT)船を実装しWisp



ます。



GIF






次のオプションがあります。





終了



そして、ここでゲームのすべてのコンテンツの実装が完了しました。記事の最後の2部では、主に手動でコンテンツを追加することに関連した多くの演習がありました。これは一部の人にとって非常に退屈に思えるかもしれないので、この作業はそのようなコンテンツの実装が好きかどうかの良い指標です。ゲームの開発の大部分は、単純にそのような要素を追加することです。そのため、まったく気に入らない場合は、後よりも早く見つける方が良いでしょう。



次のパートでは、プレーヤーがこれらすべての受動的スキルを表示するスキルツリーを見ていきます。スキルツリーが機能するために必要なすべての作成に焦点を当てますが、ツリー自体の作成(たとえば、ノードの配置と接続)は完全にタスクのままです。これは、ゲームにコンテンツを手動で追加するだけで、特に複雑なことは何もしない瞬間の1つです。






この一連のチュートリアルをお楽しみいただける場合は、今後同様のことを書いてください。





itch.ioのチュートリアルを購入すると、ゲームの完全なソースコード、パート1から9の演習への回答、チュートリアルの一部に分割されたコード(コードは各パートの終わりに見えるはずです)およびキーにアクセスできます。 Steam上のゲーム。



All Articles