മോണോഗെയിമിലെ 2D കൂട്ടിയിടി കണ്ടെത്തൽ: അടിസ്ഥാന ദീർഘചതുരങ്ങൾ മുതൽ സീറോ-അലോക്കേഷൻ പിക്സൽ-പെർഫെക്റ്റ് സിസ്റ്റങ്ങൾ വരെ
Unity അല്ലെങ്കിൽ Godot പോലുള്ള വിഷ്വൽ എഞ്ചിനുകളിൽ 2D ഗെയിമുകൾ നിർമ്മിക്കുമ്പോൾ, കൂട്ടിയിടി കണ്ടെത്തൽ പലപ്പോഴും ഇൻസ്പെക്ടർ ചെക്ക്ബോക്സുകളുടെ ഒരു പരമ്പര പോലെ അനുഭവപ്പെടുന്നു: നിങ്ങൾ ഒരു BoxCollider2D അല്ലെങ്കിൽ CircleCollider2D യിൽ തട്ടി ഒരു Rigidbody അറ്റാച്ചുചെയ്യുക, കൂടാതെ ആന്തരിക ഭൗതികശാസ്ത്ര ഘട്ടം മൊബൈൽ ഉപകരണങ്ങളിൽ ഇടറിപ്പോകില്ലെന്ന് പ്രതീക്ഷിക്കുന്നു.
MonoGame, C# എന്നിവയിൽ, എന്നിരുന്നാലും, നിങ്ങൾ പൂർണ നിയന്ത്രണത്തിലാണ്. നിങ്ങളുടെ ഫ്രെയിം ബഡ്ജറ്റ് മോഷ്ടിക്കുന്ന നിഗൂഢമായ മാലിന്യ ശേഖരണ (GC) സ്പൈക്കുകളൊന്നും മറഞ്ഞിരിക്കുന്ന ഭൗതികശാസ്ത്രത്തിന് മുകളിലോ ആവശ്യമില്ലാത്ത റൊട്ടേഷണൽ ജഡത്വമോ ഇല്ല.
Arar ഗെയിമുകളിൽ, ഞങ്ങൾ നിർമ്മിച്ചപ്പോൾ തടഞ്ഞു: Pixel Panzer, Paint Trek, ഞങ്ങളുടെ ആർക്കേഡ് ഗെയിം ലൂപ്പുകൾക്ക് നൂറുകണക്കിന് അതിവേഗ ശത്രു ബുള്ളറ്റുകൾ, പൊട്ടിത്തെറിക്കുന്ന ഇഷ്ടിക ഗ്രിഡുകൾ, കറങ്ങുന്ന ടാങ്ക് ട്യൂററ്റുകൾ, ഫൈറ്റർ ജെറ്റ് ഫ്ളൈബൈകൾ, 1 Android 0 ഉപകരണങ്ങളിൽ Flybys, and particle shields എന്നിവയ്ക്ക് ഉടനീളം 1 Android 0-ൽ Flyby-ഉം FPC 60 ഉപകരണങ്ങളും ആവശ്യമാണ്. ഒരു പൊതു-ഉദ്ദേശ്യ ഭൗതികശാസ്ത്ര എഞ്ചിൻ ചോദ്യത്തിന് പുറത്തായിരുന്നു - ഞങ്ങൾക്ക് ഒരു ഉദ്ദേശ്യ-നിർമ്മിത, ടൈയേർഡ് കൂട്ടിയിടി ആർക്കിടെക്ചർ ആവശ്യമാണ്.
ഈ സമഗ്രമായ, കോഡ്-ഡ്രൈവ് ഗൈഡിൽ, ഞങ്ങൾ MonoGame കൂട്ടിയിടിയുടെ ('Rectangle.Intersects' with real Bullet and Enimy sprites) സമ്പൂർണ്ണ സർക്കിൾ പരിശോധനകൾ, മിക്സഡ് ക്ലാമ്പിംഗ്, ആൻ്റി-ടണലിംഗ് റേകാസ്റ്റുകൾ, കോൾ-ഗ്രേഡ് **P, പ്രൊഡക്ഷൻ-ഗ്രേഡ് **P എന്നിവ വരെ നിർമ്മിക്കും. സീറോ-അലോക്കേഷൻ സ്പേഷ്യൽ ഗ്രിഡുകൾ മൊബൈൽ ജിസി അതിജീവനത്തിനായി ഒപ്റ്റിമൈസ് ചെയ്തു.
1. ഫൗണ്ടേഷൻ: ഒരു ലളിതമായ മോണോഗെയിം സ്പ്രൈറ്റ് ശ്രേണി
കൂട്ടിയിടികൾ കണ്ടെത്തുന്നതിന് മുമ്പ്, ഞങ്ങൾക്ക് ക്ലീൻ ഗെയിം എൻ്റിറ്റികൾ ആവശ്യമാണ്. MonoGame-ൽ, ഒരു എൻ്റിറ്റിക്ക് അടിസ്ഥാനപരമായി ഒരു സ്ഥാനവും ഘടനയും അതിരുകളുള്ള ദീർഘചതുരവും ഉണ്ട്.
ഞങ്ങളുടെ ഗെയിമുകളിലുടനീളം ഉപയോഗിക്കുന്ന അടിസ്ഥാന എൻ്റിറ്റി ആർക്കിടെക്ചർ ഇതാ:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
public class Sprite
{
public Vector2 Position;
public Texture2D Texture;
public Color Tint = Color.White;
public bool IsActive = true;
// The raw Axis-Aligned Bounding Box (AABB)
public virtual Rectangle Bounds => new Rectangle(
(int)Position.X,
(int)Position.Y,
Texture != null ? Texture.Width : 0,
Texture != null ? Texture.Height : 0
);
public virtual void Draw(SpriteBatch spriteBatch)
{
if (!IsActive || Texture == null) return;
spriteBatch.Draw(Texture, Position, Tint);
}
}
ഇനി നമുക്ക് കോൺക്രീറ്റ് പ്ലേയർ, എനിമി, ബുള്ളറ്റ് ക്ലാസുകൾ സൃഷ്ടിക്കാം:
public class Bullet : Sprite
{
public Vector2 Velocity;
public int Damage = 25;
public void Update(GameTime gameTime)
{
Position += Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
}
}
public class Enemy : Sprite
{
public int Health = 100;
public void TakeDamage(int damage)
{
Health -= damage;
if (Health <= 0)
{
IsActive = false;
}
}
}
public class Player : Sprite
{
public int Health = 100;
public bool IsInvulnerable = false;
public void TakeDamage(int damage)
{
if (IsInvulnerable) return;
Health -= damage;
}
}
ഈ എൻ്റിറ്റികൾ ഉള്ളതിനാൽ, ഏറ്റവും ലളിതമായ രീതിയിൽ നിന്ന് ആരംഭിച്ച്, അവ തമ്മിലുള്ള കൂട്ടിയിടികൾ എങ്ങനെ കണ്ടെത്താമെന്ന് നമുക്ക് പര്യവേക്ഷണം ചെയ്യാം.
2. ലെവൽ 1: ഏറ്റവും ലളിതമായ കൂട്ടിയിടി - ദീർഘചതുരം. ഇൻ്റർസെക്ടുകൾ (AABB)
മോണോഗെയിമിലെ ഏറ്റവും അടിസ്ഥാനപരമായ 2D കൂട്ടിയിടി പരിശോധന ആക്സിസ്-അലൈൻഡ് ബൗണ്ടിംഗ് ബോക്സ് (AABB) ടെസ്റ്റാണ്. "ആക്സിസ് അലൈൻഡ്" എന്ന പദത്തിൻ്റെ അർത്ഥം ദീർഘചതുരത്തിൻ്റെ അരികുകൾ സ്ക്രീനിൻ്റെ \(X\), \(Y\) എന്നീ അക്ഷങ്ങൾക്ക് സമാന്തരമാണ് (റൊട്ടേഷൻ ഇല്ല).
മോണോഗെയിം വേഗതയേറിയതും അന്തർനിർമ്മിതവുമായ ഒരു രീതി നൽകുന്നു: Rectangle.Intersects(Rectangle value).
എങ്ങനെയാണ് `ദീർഘചതുരം. ഇൻ്റർസെക്ട്സ്' ഹൂഡിന് കീഴിൽ പ്രവർത്തിക്കുന്നത്
ഉപരിതലത്തിന് താഴെ, മോണോഗെയിം നാല് പൂർണ്ണസംഖ്യ താരതമ്യങ്ങൾ നടപ്പിലാക്കുന്നു:
public bool Intersects(Rectangle value)
{
return value.Left < this.Right &&
this.Left < value.Right &&
value.Top < this.Bottom &&
this.Top < value.Bottom;
}
നാല് വ്യവസ്ഥകളും പാലിച്ചാൽ, ദീർഘചതുരങ്ങൾ ഓവർലാപ്പ് ചെയ്യുന്നു. ഒരു വ്യവസ്ഥ പോലും പരാജയപ്പെട്ടാൽ, ഒരു ശൂന്യമായ അക്ഷം അവയെ വേർതിരിക്കുന്നു, കൂട്ടിയിടി സാധ്യമല്ല.
യഥാർത്ഥ ഗെയിംപ്ലേ കോഡ്: ഗെയിം1.അപ്ഡേറ്റ് എന്നതിലെ ബുള്ളറ്റ് വേഴ്സസ് എനിമി
നിങ്ങളുടെ പ്രധാന മോണോഗെയിം അപ്ഡേറ്റ് ലൂപ്പിനുള്ളിലെ സജീവ ബുള്ളറ്റുകളുടെ ലിസ്റ്റും സജീവ ശത്രുക്കളുടെ ലിസ്റ്റും തമ്മിലുള്ള കൂട്ടിയിടി പരിശോധിക്കുന്നത് ഇങ്ങനെയാണ്:
public class Game1 : Game
{
private List<Bullet> _bullets = new List<Bullet>();
private List<Enemy> _enemies = new List<Enemy>();
protected override void Update(GameTime gameTime)
{
// 1. Update bullet and enemy positions
foreach (var bullet in _bullets) bullet.Update(gameTime);
// 2. Collision Check: Bullets vs Enemies
for (int b = _bullets.Count - 1; b >= 0; b--)
{
var bullet = _bullets[b];
if (!bullet.IsActive) continue;
for (int e = _enemies.Count - 1; e >= 0; e--)
{
var enemy = _enemies[e];
if (!enemy.IsActive) continue;
// The AABB check!
if (bullet.Bounds.Intersects(enemy.Bounds))
{
// Collision occurred!
enemy.TakeDamage(bullet.Damage);
bullet.IsActive = false;
// Remove inactive bullet immediately
_bullets.RemoveAt(b);
if (!enemy.IsActive)
{
_enemies.RemoveAt(e);
}
// A bullet can only hit one enemy; break the inner loop
break;
}
}
}
base.Update(gameTime);
}
}
പ്രകടന നുറുങ്ങ്: ഞങ്ങൾ പിന്നോട്ട് ആവർത്തിക്കുന്നത് ശ്രദ്ധിക്കുക (
for (int i = list. Count - 1; i >= 0; i--))! നിങ്ങൾforeachഉപയോഗിക്കുകയും_bullets.Remove(bullet)എന്ന് വിളിക്കാൻ ശ്രമിക്കുകയും ചെയ്താൽ, C# ഒരുInvalidOperationException: Collection was modifiedഎറിയുന്നു. പിന്നിലേക്ക് ആവർത്തിക്കുന്നത് മെമ്മറി റീ-ഇൻഡക്സിംഗ് പ്രശ്നങ്ങളില്ലാതെ സുരക്ഷിതമായ ഘടകങ്ങൾ നീക്കംചെയ്യാൻ അനുവദിക്കുന്നു.
ആർക്കേഡ് രഹസ്യം: ഇൻഫ്ലേറ്റ് വഴി "ഫെയർ ഹിറ്റ്ബോക്സുകൾ"
ബ്ലോക്ക്ഡ്: പിക്സൽ പാൻസർ പോലുള്ള റെട്രോ ഗെയിമുകളിൽ, സ്പ്രൈറ്റ് ടെക്സ്ചറുകളിൽ പലപ്പോഴും സുതാര്യമായ മാർജിനുകൾ അല്ലെങ്കിൽ ആൻ്റിന സ്പൈക്കുകൾ ഉൾപ്പെടുന്നു. ഒരു ബുള്ളറ്റ് അതിൻ്റെ ടെക്സ്ചറിൻ്റെ ശൂന്യമായ സുതാര്യമായ മൂലയിൽ സ്പർശിച്ചതിനാൽ കളിക്കാരൻ്റെ ടാങ്ക് പൊട്ടിത്തെറിച്ചാൽ, കളിക്കാരന് വഞ്ചിക്കപ്പെട്ടതായി അനുഭവപ്പെടും.
കൂട്ടിയിടി പ്രതികരണാത്മകവും ന്യായയുക്തവുമാക്കാൻ, ഗെയിമുകൾ സ്പ്രൈറ്റിനുള്ളിൽ Rectangle.Inflate ഉപയോഗിച്ച് ഒരു ചെറിയ Hitbox ഉപയോഗിക്കുന്നു:
public class EnemyTank : Enemy
{
// Shrink the bounding box by 6 pixels on all sides for fair collision
public override Rectangle Bounds
{
get
{
Rectangle raw = base.Bounds;
raw.Inflate(-6, -6); // Reduces width and height by 12px
return raw;
}
}
}
3. ലെവൽ 2: സർക്കിൾ-ടു-സർക്കിൾ കൂട്ടിയിടി (റൊട്ടേഷണൽ ഇമ്മ്യൂണിറ്റി)
സ്റ്റാറ്റിക് ബ്ലോക്കുകൾക്കും ഗ്രിഡ് ടൈലുകൾക്കും ദീർഘചതുരങ്ങൾ മികച്ച രീതിയിൽ പ്രവർത്തിക്കുന്നു, എന്നാൽ സ്പ്രൈറ്റുകൾ തിരിയുമ്പോൾ അവ പരാജയപ്പെടുന്നു. പെയിൻ്റ് ട്രെക്കിൽ ഒരു നോൺ-സ്ക്വയർ സ്പേസ്ഷിപ്പ് കറങ്ങുമ്പോൾ, സ്പിന്നിംഗ് കോണുകൾ വലയം ചെയ്യുന്നതിനായി ഒരു അച്ചുതണ്ട് വിന്യസിച്ചിരിക്കുന്ന ബൗണ്ടിംഗ് ബോക്സ് വികസിക്കണം, ഇത് ശൂന്യമായ വായുവിൽ നിരാശാജനകമായ "ഫാൻ്റം കൂട്ടിയിടി"ക്ക് കാരണമാകുന്നു.
വൃത്താകൃതിയിലുള്ള ഛിന്നഗ്രഹങ്ങൾ, ഹോമിംഗ് എനർജി ഓർബുകൾ, ഭ്രമണം ചെയ്യുന്ന ബഹിരാകാശ പേടകങ്ങൾ എന്നിവയ്ക്ക് ബൗണ്ടിംഗ് സർക്കിളുകൾ അനുയോജ്യമായ പരിഹാരമാണ്.
സ്ക്വയർ റൂട്ട് ട്രാപ്പ്
രണ്ട് സർക്കിളുകൾ കൂട്ടിമുട്ടുന്നത് അവയുടെ കേന്ദ്രങ്ങൾ തമ്മിലുള്ള ദൂരം അവയുടെ ആരങ്ങളുടെ ആകെത്തുകയേക്കാൾ കുറവോ തുല്യമോ ആയിരിക്കുമ്പോൾ:
\(\text{Distance}(C_A, C_B) \le r_A + r_B\)
കോഡിൽ, യൂക്ലിഡിയൻ ദൂരം കണക്കാക്കുന്നത് Math.Sqrt (അല്ലെങ്കിൽ Vector2.Distance) ഉപയോഗിക്കുന്നു. എന്നിരുന്നാലും, 200 പ്രൊജക്ടൈലുകളുള്ള ഒരു ലൂപ്പിൽ സ്ക്വയർ റൂട്ടുകൾ കണക്കാക്കുന്നത് നൂറുകണക്കിന് അനാവശ്യ സിപിയു സൈക്കിളുകളെ കത്തിക്കുന്നു!
സ്ക്വയർ ദൂരം **സ്ക്വയർ റേഡിയസ് തുകയുമായി താരതമ്യം ചെയ്യുന്നതിലൂടെ, ഞങ്ങൾ സ്ക്വയർ റൂട്ട് പൂർണ്ണമായും ഇല്ലാതാക്കുന്നു:
\(\text{DistanceSquared} \le (r_A + r_B)^2\)
മോണോഗെയിം നടപ്പിലാക്കൽ: സർക്കിൾ വേഴ്സസ് സർക്കിൾ
public struct Circle
{
public Vector2 Center;
public float Radius;
public Circle(Vector2 center, float radius)
{
Center = center;
Radius = radius;
}
public bool Intersects(Circle other)
{
float radiusSum = this.Radius + other.Radius;
// MonoGame built-in Vector2.DistanceSquared
return Vector2.DistanceSquared(this.Center, other.Center) <= (radiusSum * radiusSum);
}
}
ഇപ്പോൾ ഇത് ഒരു എൻ്റിറ്റിയിലേക്ക് നേരിട്ട് സംയോജിപ്പിക്കുക:
public class PaintTrekFighter : Sprite
{
public float CollisionRadius = 18f;
public Vector2 Center => Position + new Vector2(Texture.Width * 0.5f, Texture.Height * 0.5f);
public Circle BoundingCircle => new Circle(Center, CollisionRadius);
public bool CollidesWith(PaintTrekFighter other)
{
return this.BoundingCircle.Intersects(other.BoundingCircle);
}
}
സീറോ സ്ക്വയർ റൂട്ടുകൾ, സ്പ്രൈറ്റ് റൊട്ടേഷൻ പ്രതിരോധം, മിന്നൽ വേഗത്തിലുള്ള.
4. ലെവൽ 3: മിക്സഡ് ഷേപ്പുകൾ - സർക്കിൾ വേഴ്സസ്. ബോക്സ് (MathHelper.Clamp)
പെയിൻ്റ് ട്രെക്കിൽ ഒരു വൃത്താകൃതിയിലുള്ള ബഹിരാകാശ പേടകം ചതുരാകൃതിയിലുള്ള പ്രതിരോധ തടസ്സങ്ങളുടെ ഒരു ഇറുകിയ ഭ്രമണപഥത്തിലൂടെ നാവിഗേറ്റ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ ബ്ലോക്ക്ഡ്: Pixel Panzer എന്നതിലെ ഒരു വൃത്താകൃതിയിലുള്ള ബുള്ളറ്റ് ഒരു ചതുര ബ്ലോക്കിൽ പതിക്കുമ്പോൾ എന്ത് സംഭവിക്കും?
നമുക്ക് വൃത്തം വേഴ്സസ് ദീർഘചതുരം കൂട്ടിയിടി ആവശ്യമാണ്.
ക്ലാമ്പിംഗ് അൽഗോരിതം
വൃത്തത്തിൻ്റെ കേന്ദ്രത്തോട് ഏറ്റവും അടുത്തുള്ള ദീർഘചതുരത്തിലെ പോയിൻ്റ് കണ്ടെത്തുക, തുടർന്ന് ആ ഏറ്റവും അടുത്ത ബിന്ദുവിൽ നിന്ന് കേന്ദ്രത്തിലേക്കുള്ള ദൂരം വൃത്തത്തിൻ്റെ ദൂരത്തേക്കാൾ കുറവാണോ എന്ന് പരിശോധിക്കുക എന്നതാണ് തന്ത്രം.
MonoGame-ൻ്റെ MathHelper.Clamp ഇതിനെ നിസ്സാരമാക്കുന്നു:
public static class Collision2D
{
public static bool CircleIntersectsRectangle(Circle circle, Rectangle rect)
{
// Find the closest point on the rectangle to the circle center
float closestX = MathHelper.Clamp(circle.Center.X, rect.Left, rect.Right);
float closestY = MathHelper.Clamp(circle.Center.Y, rect.Top, rect.Bottom);
// Vector from closest point to circle center
float distanceX = circle.Center.X - closestX;
float distanceY = circle.Center.Y - closestY;
// Check squared distance against squared radius
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared <= (circle.Radius * circle.Radius);
}
}
പൂജ്യം അലോക്കേഷനുകളും ഉയർന്ന കൃത്യതയും ഉള്ള ചതുരാകൃതിയിലുള്ള ഇഷ്ടികകൾക്കെതിരെ നിങ്ങൾക്ക് ഇപ്പോൾ പ്ലേയർ ഷീൽഡുകൾ പരീക്ഷിക്കാം!
5. ലെവൽ 4: തുടർച്ചയായ കൂട്ടിയിടി കണ്ടെത്തൽ (CCD) & സ്വെപ്റ്റ് കിരണങ്ങൾ
നിങ്ങളുടെ ഗെയിമിൽ എപ്പോഴെങ്കിലും ഒരു ഹൈപ്പർ-വെലോസിറ്റി സ്നൈപ്പർ റൗണ്ടോ റെയിൽഗൺ ലേസർ ഉപയോഗിച്ചോ വെടിവെച്ചിട്ടുണ്ടോ, ബുള്ളറ്റ് കേടുപാടുകൾ വരുത്താതെ ഒരു നേർത്ത ശത്രു കപ്പലിലൂടെ മാന്ത്രികമായി കടന്നുപോകുന്നത് കാണാൻ മാത്രം?
ഈ ബഗ് ടണലിംഗ് എന്നാണ് അറിയപ്പെടുന്നത്.
വ്യതിരിക്ത ഗെയിമുകൾ സമയ ഘട്ടങ്ങളിൽ അപ്ഡേറ്റ് ചെയ്യുന്നതിനാൽ (\(\Delta t = 16.6\text{ms}\) 60 FPS-ൽ), സെക്കൻഡിൽ 1,800 പിക്സലിൽ ചലിക്കുന്ന ഒരു ഒബ്ജക്റ്റ് ഒറ്റ ഫ്രെയിമിൽ **30 പിക്സലുകൾ സഞ്ചരിക്കുന്നു. ശത്രു ഹൾ കനം 15 പിക്സൽ മാത്രമാണെങ്കിൽ, ബുള്ളറ്റ് ഫ്രെയിം 1 ൽ ശത്രുവിന് മുന്നിലും ഫ്രെയിം 2 ൽ ശത്രുവിന് പൂർണ്ണമായും പിന്നിലുമായിരുന്നു.
Frame 1: [ Bullet ] ---> | Enemy Wall |
Frame 2: | Enemy Wall | ---> [ Bullet ]
(NO HIT DETECTED!)
പരിഹാരം: സ്വീപ്റ്റ് സെഗ്മെൻ്റ് വേഴ്സസ്. ബോക്സ് (സ്ലാബ് രീതി)
ഒരൊറ്റ പോയിൻ്റ് പരീക്ഷിക്കുന്നതിനുപകരം, ഫ്രെയിം 1 (മുമ്പത്തെ സ്ഥാനം)-ലെ ബുള്ളറ്റിൻ്റെ സ്ഥാനം ഫ്രെയിം 2-ലേക്ക് (നിലവിലെ സ്ഥാനം) ബന്ധിപ്പിക്കുന്ന മുഴുവൻ ലൈൻ സെഗ്മെൻ്റും ഞങ്ങൾ പരിശോധിക്കുന്നു.
ഞങ്ങളുടെ കമ്പാനിയൻ തലക്കെട്ടിൽ നിന്നുള്ള പ്രൊഡക്ഷൻ റേകാസ്റ്റിംഗ് സ്ലാബ്-ഇൻ്റർസെക്ഷൻ രീതി ഇതാ സ്പൈറൽ വാർ:
public static class ContinuousCollision
{
public static bool IntersectsSweptRay(Vector2 rayStart, Vector2 rayEnd, Rectangle box, out Vector2 hitPoint)
{
hitPoint = Vector2.Zero;
Vector2 direction = rayEnd - rayStart;
float tMin = 0f;
float tMax = 1f;
// Clip against X slabs
if (MathF.Abs(direction.X) > 1e-6f)
{
float invX = 1f / direction.X;
float t1 = (box.Left - rayStart.X) * invX;
float t2 = (box.Right - rayStart.X) * invX;
if (t1 > t2) (t1, t2) = (t2, t1);
tMin = MathF.Max(tMin, t1);
tMax = MathF.Min(tMax, t2);
if (tMin > tMax) return false;
}
else if (rayStart.X < box.Left || rayStart.X > box.Right)
{
return false;
}
// Clip against Y slabs
if (MathF.Abs(direction.Y) > 1e-6f)
{
float invY = 1f / direction.Y;
float t1 = (box.Top - rayStart.Y) * invY;
float t2 = (box.Bottom - rayStart.Y) * invY;
if (t1 > t2) (t1, t2) = (t2, t1);
tMin = MathF.Max(tMin, t1);
tMax = MathF.Min(tMax, t2);
if (tMin > tMax) return false;
}
else if (rayStart.Y < box.Top || rayStart.Y > box.Bottom)
{
return false;
}
hitPoint = rayStart + direction * tMin;
return true;
}
}
ബ്ലോക്ക് ചെയ്തത്: പിക്സൽ പാൻസർ എന്നതിൽ, കളിക്കാരൻ്റെ തുടർച്ചയായ ലേസർ ബീം സ്കിൽ ഈ കൃത്യമായ റേകാസ്റ്റ് ഉപയോഗിച്ച് ഒരു കൂട്ടിയിടി പോലും നഷ്ടപ്പെടാതെ അവരോഹണ ബ്ലോക്കുകളുടെ നിരകളിലൂടെ സ്ലൈസ് ചെയ്യുന്നു.
6. ലെവൽ 5: പ്രൊഡക്ഷൻ-ഗ്രേഡ് പിക്സൽ-പെർഫെക്റ്റ് കൊളിഷൻ ഡിറ്റക്ഷൻ
ഇപ്പോൾ നമ്മൾ 2D കൃത്യതയുടെ ആത്യന്തിക തലത്തിലെത്തി: Pixel-Perfect Collision.
ഒരു റെട്രോ ടാങ്ക് ഷൂട്ടറിലോ സ്പേസ്ഷിപ്പ് ഡോഗ്ഫൈറ്റിലോ, ക്രമരഹിതമായ ആകൃതികൾ (ടാങ്ക് ബാരലുകൾ, ചിറകുകൾ, കോക്ക്പിറ്റ് കോക്ക്പിറ്റുകൾ) സ്പ്രൈറ്റ് ടെക്സ്ചറിൽ സുതാര്യമായ പിക്സലുകളാൽ ചുറ്റപ്പെട്ടിരിക്കുന്നു. ഒരു ശത്രു മിസൈൽ ആ സുതാര്യമായ സ്ഥലത്ത് പതിക്കുമ്പോൾ, കളിക്കാർ ഉടനടി ശ്രദ്ധിക്കുന്നു.
ഓവർലാപ്പുചെയ്യുന്ന ടെക്സ്ചറുകളുടെ യഥാർത്ഥ ആൽഫ (സുതാര്യത) ചാനലുകൾ Pixel-perfect collision പരിശോധിക്കുന്നു. ഒരേ വേൾഡ് കോർഡിനേറ്റിൽ സുതാര്യമല്ലാത്ത രണ്ട് പിക്സലുകൾ ഓവർലാപ്പ് ചെയ്യുകയാണെങ്കിൽ, ഒരു യഥാർത്ഥ ഫിസിക്കൽ ഹിറ്റ് സംഭവിച്ചു.
മാരകമായ തെറ്റ്: അപ്ഡേറ്റ്() എന്നതിനുള്ളിലെ GetData
പല ട്യൂട്ടോറിയലുകളും തുടക്കക്കാർക്ക് ഇത് ചെയ്യാൻ നിർദ്ദേശിക്കുന്നു:
// DO NOT DO THIS!
Color[] dataA = new Color[textureA.Width * textureA.Height];
textureA.GetData(dataA); // STALLS GPU, CREATES MASSIVE GC LAG!
റൺടൈമിൽ Texture2D.GetData() എന്ന് വിളിക്കുന്നത്, GPU പൈപ്പ്ലൈൻ ഫ്ലഷ് ചെയ്യുന്നതിനായി കാത്തിരിക്കാൻ CPU-നെ പ്രേരിപ്പിക്കുകയും, ടെക്സ്ചർ മെമ്മറി ബസിനു മുകളിലൂടെ തിരികെ വലിക്കുകയും ചെയ്യുന്നു. ഓരോ ഫ്രെയിമും ഡസൻ കണക്കിന് സ്പ്രൈറ്റുകൾക്കായി ഇത് ചെയ്യുന്നത് നിങ്ങളുടെ ഗെയിമിനെ 60 FPS-ൽ നിന്ന് 5 FPS-ലേക്ക് മാറ്റുകയും Android ANR-കൾ ഉടനടി പ്രവർത്തനക്ഷമമാക്കുകയും ചെയ്യും!
തടഞ്ഞു: Pixel Panzer എന്നതിൽ നിന്നുള്ള യുദ്ധ-പരിശോധിച്ച പരിഹാരം
ബ്ലോക്ക് ചെയ്തത്: Pixel Panzer എന്നതിൽ, ഞങ്ങളുടെ Sprite.cs 5 നിർണായക നിയമങ്ങൾ ഉൾക്കൊള്ളുന്ന ഒരു അൾട്രാ ഒപ്റ്റിമൈസ് ചെയ്ത IntersectsPixel ദിനചര്യ നടപ്പിലാക്കുന്നു:
- സ്റ്റാറ്റിക് കളർ അറേ കാഷെ: ടെക്സ്ചർ ലോഡുചെയ്യുമ്പോൾ
GetDataകൃത്യമായി ഒരു പ്രാവശ്യം വിളിക്കുകയും ഒരുനിഘണ്ടു<Texture2D, Color[]>-ൽ സംഭരിക്കുകയും ചെയ്യുന്നു. - AABB ഏർലി എക്സിറ്റ് ഗാർഡ്:
ബൗണ്ട്സ്. ഇൻ്റർസെക്ട്സ് (മറ്റ്. ബൗണ്ട്സ്)തെറ്റാണെങ്കിൽ, ഞങ്ങൾ ഉടനടി ജാമ്യം നേടുന്നു. ഒരൊറ്റ പിക്സൽ പരിശോധിക്കുന്നതിന് മുമ്പ് 99% ചെക്കുകളും ഒഴിവാക്കപ്പെടും. - ടെക്സ്ചർ അറ്റ്ലസ് &
സോഴ്സ് റെക്ടാംഗിൾപിന്തുണ: സോഴ്സ് റെക്ടാംഗിൾ ഓഫ്സെറ്റുകൾ ഉപയോഗിച്ച് ടെക്സ്ചർ അറ്റ്ലസുകളിലേക്ക് പാക്ക് ചെയ്ത സ്പ്രൈറ്റുകൾ കൈകാര്യം ചെയ്യുന്നു. - കണക്കെടുത്ത ഓവർലാപ്പ് വിൻഡോ: രണ്ട് സ്പ്രൈറ്റുകൾക്കും ഇടയിലുള്ള കൃത്യമായ ഇൻ്റർസെക്ഷൻ ദീർഘചതുരത്തിന് മുകളിലൂടെ മാത്രമേ ഞങ്ങൾ ലൂപ്പ് ചെയ്യുകയുള്ളൂ (
Math.Max(a.Top, b.Top), മുതലായവ). - ആൽഫ ഷോർട്ട് സർക്യൂട്ട്: സ്പ്രൈറ്റ് എയുടെ പിക്സൽ സുതാര്യമാണെങ്കിൽ (
എ <= 20), സ്പ്രൈറ്റ് ബി പൂർണ്ണമായും ഒഴിവാക്കപ്പെടും. - പ്രോപ്പർട്ടി ഹോയിസ്റ്റിംഗ്: നെസ്റ്റഡ് ലൂപ്പുകളിൽ വെർച്വൽ പ്രോപ്പർട്ടികൾ (
ബൗണ്ട്സ്) ആക്സസ് ചെയ്യുന്നത് ആയിരക്കണക്കിന് സ്ട്രക്റ്റ് കോപ്പികൾ സൃഷ്ടിക്കുന്നു. ലൂപ്പിൽ പ്രവേശിക്കുന്നതിന് മുമ്പ് ഞങ്ങൾ അവയെ ലോക്കൽ സ്റ്റാക്ക് വേരിയബിളുകളിൽ സംഭരിക്കുന്നു.
പൂർണ്ണമായ, പ്രൊഡക്ഷൻ-റെഡി കോഡ് ഇതാ:
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
public class AdvancedSprite : Sprite
{
private static readonly Dictionary<Texture2D, Color[]> _textureDataCache = new();
public Rectangle? SourceRectangle { get; set; }
public static Color[] GetCachedTextureData(Texture2D texture)
{
if (!_textureDataCache.TryGetValue(texture, out var data))
{
data = new Color[texture.Width * texture.Height];
texture.GetData(data);
_textureDataCache[texture] = data;
}
return data;
}
public bool IntersectsPixel(AdvancedSprite other)
{
// 1. Hoist bounds into local stack variables
Rectangle a = this.Bounds;
Rectangle b = other.Bounds;
// 2. Fast AABB Broad-Phase check
if (!a.Intersects(b)) return false;
// Fallback to rectangle check if textures are missing
if (this.Texture == null || other.Texture == null) return true;
// 3. Retrieve pre-cached color arrays (Zero GPU latency)
Color[] dataA = GetCachedTextureData(this.Texture);
Color[] dataB = GetCachedTextureData(other.Texture);
int aTexW = this.Texture.Width;
int bTexW = other.Texture.Width;
// Atlas / SourceRectangle offset mapping
bool aHasSrc = this.SourceRectangle.HasValue;
Rectangle aSrc = aHasSrc ? this.SourceRectangle.Value : Rectangle.Empty;
int aOriginX = aHasSrc ? aSrc.X : 0;
int aOriginY = aHasSrc ? aSrc.Y : 0;
int aSpanX = aHasSrc ? aSrc.Width : this.Texture.Width;
int aSpanY = aHasSrc ? aSrc.Height : this.Texture.Height;
bool bHasSrc = other.SourceRectangle.HasValue;
Rectangle bSrc = bHasSrc ? other.SourceRectangle.Value : Rectangle.Empty;
int bOriginX = bHasSrc ? bSrc.X : 0;
int bOriginY = bHasSrc ? bSrc.Y : 0;
int bSpanX = bHasSrc ? bSrc.Width : other.Texture.Width;
int bSpanY = bHasSrc ? bSrc.Height : other.Texture.Height;
// 4. Calculate the overlapping sub-rectangle
int top = Math.Max(a.Top, b.Top);
int bottom = Math.Min(a.Bottom, b.Bottom);
int left = Math.Max(a.Left, b.Left);
int right = Math.Min(a.Right, b.Right);
// 5. Scan only overlapping pixels
for (int y = top; y < bottom; y++)
{
// Row offsets hoisted outside the inner X loop!
int colorA_Y = aOriginY + (int)((y - a.Y) / (float)a.Height * aSpanY);
int colorB_Y = bOriginY + (int)((y - b.Y) / (float)b.Height * bSpanY);
int rowA = colorA_Y * aTexW;
int rowB = colorB_Y * bTexW;
for (int x = left; x < right; x++)
{
int colorA_X = aOriginX + (int)((x - a.X) / (float)a.Width * aSpanX);
// Early Bailout: If Pixel A is transparent, skip B entirely!
if (dataA[colorA_X + rowA].A <= 20) continue;
int colorB_X = bOriginX + (int)((x - b.X) / (float)b.Width * bSpanX);
// If Pixel B is also opaque, we have confirmed contact!
if (dataB[colorB_X + rowB].A > 20)
{
return true;
}
}
}
return false;
}
}
ആധുനിക Android ഹാർഡ്വെയറിലും ഡെസ്ക്ടോപ്പ് സിസ്റ്റങ്ങളിലും 0.1 മില്ലിസെക്കൻഡിൽ താഴെ സമയത്തിനുള്ളിൽ ഈ ദിനചര്യ നടപ്പിലാക്കുന്നു.
7. ലെവൽ 6: ബ്രോഡ്-ഫേസ് സ്പേഷ്യൽ ഗ്രിഡ് & സീറോ-അലോക്കേഷൻ മൊബൈൽ ജിസി
നിങ്ങളുടെ ഗെയിമിന് 100 ബ്ലോക്കുകളും 60 ബുള്ളറ്റുകളും 20 ശത്രുക്കളും ഉണ്ടെന്ന് സങ്കൽപ്പിക്കുക. നെസ്റ്റഡ് ഫോർ ലൂപ്പുകൾ ഉപയോഗിച്ച് മറ്റെല്ലാ എൻ്റിറ്റിക്കെതിരെയും നിങ്ങൾ ഓരോ എൻ്റിറ്റിയും പരീക്ഷിക്കുകയാണെങ്കിൽ, നിങ്ങൾ ചെയ്യുന്നത്:
\(\frac{180 \times 179}{2} = 16,110 \text{ ഓരോ ഫ്രെയിമിനും പരിശോധനകൾ!}\)
60 FPS-ൽ, അതായത് ഓരോ സെക്കൻഡിലും ഏകദേശം 1,000,000 കൂട്ടിയിടി പരിശോധനകൾ.
നിങ്ങളുടെ ഗെയിം സ്കെയിൽ ചെയ്യാൻ, നിങ്ങൾ ബ്രോഡ്-ഫേസ് സ്പേഷ്യൽ പാർട്ടീഷനിംഗ് നടപ്പിലാക്കണം.
യൂണിഫോം സ്പേഷ്യൽ ഹാഷ് ഗ്രിഡ്
ഞങ്ങൾ സ്ക്രീനിനെ യൂണിഫോം സെല്ലുകളുടെ ഒരു 2D ഗ്രിഡായി വിഭജിക്കുന്നു (ബ്ലോക്ക് ചെയ്തത്: Pixel Panzer എന്നതിൽ, ഓരോ സെല്ലും \(150 \times 150\) pixels ആണ്). ഒരേ ഗ്രിഡ് സെല്ലുകളിൽ വസിക്കുന്ന മറ്റ് എൻ്റിറ്റികൾക്കെതിരായ കൂട്ടിയിടികൾ മാത്രമാണ് ഒരു എൻ്റിറ്റി പരിശോധിക്കുന്നത്.
മൊബൈൽ മെമ്മറി പ്രശ്നം: ജിസി ത്രഷിംഗ്
നിങ്ങളുടെ SpatialGrid ഓരോ ഫ്രെയിമിലും പുതിയ List<Point>() അല്ലെങ്കിൽ New List<Block>() സൃഷ്ടിക്കുന്നുവെങ്കിൽ, നിങ്ങൾ നിയന്ത്രിക്കപ്പെടുന്ന കൂമ്പാരത്തിൽ ഓരോ മിനിറ്റിലും മെഗാബൈറ്റ് മാലിന്യം നീക്കിവയ്ക്കുന്നു.
ആൻഡ്രോയിഡിൻ്റെ മോണോ റൺടൈമിൽ, ഇത് പതിവായി Gen-0 ഗാർബേജ് ശേഖരണങ്ങൾ ട്രിഗർ ചെയ്യുന്നു, ഇത് കാരണമാകുന്നു:
മോണോ റൺടൈം: നേറ്റീവ് ലോക്ക് തർക്കം (mono_class_is_subclass_)
ANR മുന്നറിയിപ്പുകൾ ഉപയോഗിച്ച് ഗെയിം ഇടറുകയും Google Play നിങ്ങളുടെ ഗെയിമിനെ ഫ്ലാഗ് ചെയ്യുകയും ചെയ്യുന്നു!
പരിഹാരം: വീണ്ടും ഉപയോഗിക്കാവുന്ന സ്ക്രാച്ച് ബഫറുകൾ
തടഞ്ഞു: Pixel Panzer എന്നതിലെ CollisionManager.cs-ൽ നിന്നുള്ള സീറോ-അലോക്കേഷൻ സ്പേഷ്യൽ ഹാഷ് ഗ്രിഡ് ഇതാ:
public class SpatialGridManager
{
private const int CELL_SIZE = 150;
private readonly Dictionary<Point, List<Sprite>> _grid = new();
// Zero-GC: Reusable scratch buffers allocated ONCE at startup
private readonly List<Point> _scratchCells = new();
private readonly HashSet<Sprite> _scratchCheckedEntities = new();
public void BuildGrid(List<Sprite> entities)
{
// Clear lists without re-allocating new List objects
foreach (var list in _grid.Values)
{
list.Clear();
}
foreach (var entity in entities)
{
if (!entity.IsActive) continue;
GetOccupiedCells(entity.Bounds, _scratchCells);
foreach (var cell in _scratchCells)
{
if (!_grid.TryGetValue(cell, out var list))
{
list = new List<Sprite>();
_grid[cell] = list;
}
list.Add(entity);
}
}
}
private void GetOccupiedCells(Rectangle bounds, List<Point> outCells)
{
outCells.Clear();
int startX = bounds.Left / CELL_SIZE;
int startY = bounds.Top / CELL_SIZE;
int endX = bounds.Right / CELL_SIZE;
int endY = bounds.Bottom / CELL_SIZE;
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
outCells.Add(new Point(x, y));
}
}
}
public void CheckCollisions(Player player)
{
if (player.IsInvulnerable) return;
GetOccupiedCells(player.Bounds, _scratchCells);
_scratchCheckedEntities.Clear();
foreach (var cell in _scratchCells)
{
if (_grid.TryGetValue(cell, out var entitiesInCell))
{
foreach (var other in entitiesInCell)
{
if (other == player || _scratchCheckedEntities.Contains(other)) continue;
_scratchCheckedEntities.Add(other);
// 1. Broad-phase AABB test
if (player.Bounds.Intersects(other.Bounds))
{
// 2. High-precision Pixel-Perfect test
if (player is AdvancedSprite advPlayer && other is AdvancedSprite advOther)
{
if (advPlayer.IntersectsPixel(advOther))
{
player.TakeDamage(10);
}
}
}
}
}
}
}
}
_scratchCells, _scratchCheckedEntities എന്നിവ വീണ്ടും ഉപയോഗിക്കുന്നതിലൂടെ, ഒരു ഫ്രെയിമിന് ഹീപ്പ് അലോക്കേഷൻ്റെ 0 ബൈറ്റുകൾ ഉപയോഗിച്ച് മുഴുവൻ കൂട്ടിയിടി ഘട്ടവും പ്രവർത്തിക്കുന്നു.
8. സംഗ്രഹ താരതമ്യ പട്ടിക
| സാങ്കേതികത | ഗണിത ചെലവ് | റൊട്ടേഷണൽ സപ്പോർട്ട് | ടണലിംഗ് സുരക്ഷിതമാണോ? | മോണോഗെയിമിലെ മികച്ച ഉപയോഗം |
|---|---|---|---|---|
AABB (ദീർഘചതുരം. ഇൻ്റർസെക്ടുകൾ) |
അൾട്രാ ഫാസ്റ്റ് (~4 പൂർണ്ണസംഖ്യ താരതമ്യങ്ങൾ) | ഇല്ല | ഇല്ല | ഗ്രിഡ് ബ്ലോക്കുകൾ, ബുള്ളറ്റ് ബ്രോഡ്-ഫേസ്, UI |
സർക്കിൾ (DistanceSquared) |
വളരെ വേഗം (3 muls, 0 sqrt) | അതെ (മാറ്റമില്ലാത്തത്) | ഇല്ല | വൃത്താകൃതിയിലുള്ള കപ്പലുകൾ, അഗ്നിഗോളങ്ങൾ, ഊർജ്ജ ഭ്രമണപഥങ്ങൾ |
സർക്കിൾ വേഴ്സസ് ബോക്സ് (ക്ലാമ്പ്) |
ഫാസ്റ്റ് (പ്രാദേശിക MathHelper.Clamp) | അതെ | ഇല്ല | ഇറുകിയ ചുവരുകൾ നാവിഗേറ്റ് ചെയ്യുന്ന വൃത്താകൃതിയിലുള്ള പ്ലെയർ |
| സ്വീപ്റ്റ് റേ (സ്ലാബ് സിസിഡി) | മിതമായ (പാരാമെട്രിക് റേകാസ്റ്റ്) | അതെ | അതെ | വേഗതയേറിയ സ്നൈപ്പർ ബുള്ളറ്റുകൾ, റെയിൽഗൺ, ലേസർ |
പിക്സൽ പെർഫെക്റ്റ് (ഇൻ്റർസെക്ട്സ് പിക്സൽ) |
സെലക്ടീവ് (നിയന്ത്രിത ഉപവിഭാഗം) | അതെ | ഇല്ല | ക്രമരഹിതമായ സ്പ്രൈറ്റ് കോണ്ടൂർ, ഫെയർ ഹിറ്റ്ബോക്സുകൾ |
| സ്പേഷ്യൽ ഗ്രിഡ് (സീറോ-ജിസി) | ബ്രോഡ്-ഫേസ് (\(O(N^2) \ to O(N)\)) | N/A | N/A | ഇടതൂർന്ന തിരമാലകൾ, ബുള്ളറ്റ് ഹെൽസ്, ആൻഡ്രോയിഡ് 60 FPS |
റിയൽ-വേൾഡ് പ്രൊഡക്ഷൻ ഷോകേസുകൾ: അരാർ ഗെയിമുകൾ
ഈ കൂട്ടിയിടി വാസ്തുവിദ്യകൾ സൈദ്ധാന്തിക പരീക്ഷണങ്ങളല്ല - വാണിജ്യപരമായി പുറത്തിറക്കിയ ശീർഷകങ്ങളെ ശക്തിപ്പെടുത്തുന്ന യഥാർത്ഥ എഞ്ചിനീയറിംഗ് അടിത്തറയാണ് അവ:
- തടഞ്ഞു: Pixel Panzer: Google Play, Microsoft Store എന്നിവയിലെ ഞങ്ങളുടെ റെട്രോ ടാങ്ക് അതിജീവന ആർക്കേഡ് ഗെയിം. ഞങ്ങളുടെ സമ്പൂർണ്ണ ദ്വി-ടയർ കൂട്ടിയിടി സംവിധാനത്തെ ഇത് അവതരിപ്പിക്കുന്നു: ഒരു സീറോ-അലോക്കേഷൻ സ്പേഷ്യൽ ഹാഷ് ഗ്രിഡ് ഫിൽട്ടറിംഗ് ഫാലിംഗ് ബ്ലോക്കുകൾ, കൂടാതെ ടാങ്കുകൾ, ഫൈറ്റർ ജെറ്റുകൾ, ടററ്റ് ഷെല്ലുകൾ, കളർ വീൽ എലമെൻ്റൽ വെടിമരുന്ന് എന്നിവയ്ക്കായുള്ള പിക്സൽ-പെർഫെക്റ്റ് കോൺടാക്റ്റ് പരിശോധന.
- പെയിൻ്റ് ട്രെക്ക്: റൊട്ടേഷണൽ സർക്കിൾ കൂട്ടിയിടി, തുടർച്ചയായ റേകാസ്റ്റിംഗ്, സ്വീപ്റ്റ്-വോളിയം മിസൈൽ പ്രതിരോധ സംവിധാനങ്ങൾ എന്നിവ ഉൾക്കൊള്ളുന്ന ഞങ്ങളുടെ അതിവേഗ സ്പേസ് ഷൂട്ടർ.
ഉപസംഹാരവും അടുത്ത ഘട്ടങ്ങളും
നിങ്ങളുടെ ഗെയിമിൻ്റെ ആവശ്യങ്ങളുമായി തികച്ചും പൊരുത്തപ്പെടുന്ന കൂട്ടിയിടി കണ്ടെത്തൽ രൂപകൽപ്പന ചെയ്യുന്നതിനുള്ള ശക്തി MonoGame നിങ്ങൾക്ക് നൽകുന്നു. വേഗതയേറിയ AABB ടെസ്റ്റുകൾക്ക് പിന്നിലെ വിലകൂടിയ Pixel-Perfect പരിശോധനകൾ, സർക്കിളുകൾക്കായി Squared Distances ഉപയോഗിക്കുന്നതിലൂടെ, പുനരുപയോഗിക്കാവുന്ന സ്ക്രാച്ച് ബഫറുകൾ ഉപയോഗിച്ച് മാലിന്യ ശേഖരണം ഒഴിവാക്കുന്നതിലൂടെ, നിങ്ങൾക്ക് രണ്ട് de.
ഈ കൂട്ടിയിടി സംവിധാനങ്ങൾ പ്രവർത്തനക്ഷമമായി കാണുന്നതിന് ആപ്പ് സ്റ്റോറുകളിലെ ഞങ്ങളുടെ ഗെയിമുകൾ പരിശോധിക്കുക, നിങ്ങളുടെ സ്വന്തം മോണോഗെയിം പ്രോജക്റ്റുകളിൽ ഈ പാറ്റേണുകൾ ഇന്നുതന്നെ നടപ്പിലാക്കാൻ ആരംഭിക്കുക!
സ്റ്റോർ ലിങ്കുകളും ഉറവിടങ്ങളും
- തടഞ്ഞു: Google Play-യിലെ Pixel Panzer: Android-നായി ഡൗൺലോഡ് ചെയ്യുക
- തടഞ്ഞു: Microsoft Store-ൽ Pixel Panzer: Windows-നായി ഡൗൺലോഡ് ചെയ്യുക
- MonoGame ഫ്രെയിംവർക്ക്: monogame.net
SEO കീവേഡുകളും ഹാഷ്ടാഗുകളും
** കീവേഡുകൾ:** മോണോഗെയിം 2D കൂട്ടിയിടി കണ്ടെത്തൽ, സി# ഗെയിം വികസനം, ദീർഘചതുരം. ഇൻ്റർസെക്സ് മോണോഗെയിം, പിക്സൽ പെർഫെക്റ്റ് കൊളിഷൻ സി#, സ്പേഷ്യൽ ഹാഷ് ഗ്രിഡ് മോണോഗെയിം, സ്വീപ് റേ തുടർച്ചയായ കൂട്ടിയിടി, ഇൻഡി ഗെയിം പെർഫോമൻസ് ഒപ്റ്റിമൈസേഷൻ, ലൂപ്ജി ഗെയിം ഒപ്റ്റിമൈസേഷൻ, ലൂപ്ജി ആൻഡ്രോയിഡ് ഒപ്റ്റിമൈസേഷൻ തടഞ്ഞ പിക്സൽ പാൻസർ, പെയിൻ്റ് ട്രെക്ക്.
#MonoGame #CSharp #GameDev #IndieDev #GamePhysics #DotNet #2DGameDev #MobileGameDev #PerformanceOptimization #CleanCode #BlockedPixel #ഗെയിംപ്രോഗ്രാമിംഗ്`
Hiç yorum yok:
Yorum Gönder