PageRenderTime 187ms CodeModel.GetById 137ms app.highlight 24ms RepoModel.GetById 21ms app.codeStats 0ms

/examples/Shooter/Shooter.cs

https://github.com/rsaarelm/behemoth
C# | 534 lines | 392 code | 133 blank | 9 comment | 32 complexity | 328903132493ea1d3c707cc840093700 MD5 | raw file
  1using System;
  2using System.IO;
  3using System.Diagnostics;
  4using System.Collections.Generic;
  5
  6using Tao.OpenGl;
  7using Tao.Sdl;
  8
  9using Behemoth.Util;
 10using Behemoth.Apps;
 11using Behemoth.TaoUtil;
 12
 13namespace Shooter
 14{
 15  abstract class Entity
 16  {
 17    public virtual void Display(Shooter shooter, int xOffset, int yOffset)
 18    {
 19      shooter.DrawSprite((int)X + xOffset, (int)Y + yOffset, Frame);
 20    }
 21
 22
 23    public virtual void Update(EntityManager context)
 24    {
 25    }
 26
 27
 28    public void SetHitBox(int x, int y, int w, int h)
 29    {
 30      hitBoxX = x;
 31      hitBoxY = y;
 32      hitBoxW = w;
 33      hitBoxH = h;
 34    }
 35
 36
 37    public bool Intersects(Entity other)
 38    {
 39      return Geom.RectanglesIntersect(
 40        hitBoxX + X, hitBoxY + Y, hitBoxW, hitBoxH,
 41        other.hitBoxX + other.X, other.hitBoxY + other.Y, other.hitBoxW, other.hitBoxH);
 42    }
 43
 44
 45    public double X;
 46    public double Y;
 47    public int Frame;
 48
 49    private int hitBoxX = 0;
 50    private int hitBoxY = 0;
 51    private int hitBoxW = Shooter.spriteWidth;
 52    private int hitBoxH = Shooter.spriteHeight;
 53  }
 54
 55
 56  class Explosion : Entity
 57  {
 58    public Explosion(double x, double y)
 59    {
 60      X = x;
 61      Y = y;
 62      Frame = startFrame;
 63      cooldown = rate;
 64    }
 65
 66
 67    public override void Update(EntityManager context)
 68    {
 69      if (cooldown-- <= 0)
 70      {
 71        cooldown = rate;
 72        Frame++;
 73        if (Frame == endFrame)
 74        {
 75          context.Remove(this);
 76        }
 77      }
 78    }
 79
 80
 81    private const int rate = 4;
 82    private const int startFrame = 8;
 83    private const int endFrame = 12;
 84    private int cooldown;
 85  }
 86
 87
 88  class Avatar : Entity
 89  {
 90    public Avatar()
 91    {
 92      Y = 8.0;
 93      X = Shooter.pixelWidth / 2 - Shooter.spriteWidth / 2;
 94      Frame = 16;
 95
 96      SetHitBox(6, 5, 4, 7);
 97    }
 98
 99
100    public override void Update(EntityManager context)
101    {
102      if (!IsAlive)
103      {
104        return;
105      }
106
107      if (IsShooting)
108      {
109        if (cooldown-- <= 0)
110        {
111          Fire(context);
112        }
113      }
114
115      if (IsMovingLeft && !IsMovingRight)
116      {
117        X = Math.Max(minX, X - speed);
118      }
119      else if (IsMovingRight && !IsMovingLeft)
120      {
121        X = Math.Min(maxX, X + speed);
122      }
123    }
124
125
126    public override void Display(Shooter shooter, int xOff, int yOff)
127    {
128      if (!IsAlive)
129      {
130        return;
131      }
132
133      base.Display(shooter, xOff, yOff);
134    }
135
136
137    public void Fire(EntityManager context)
138    {
139      Media.PlaySound(Shooter.playerShotFx);
140      context.Add(new AvatarShot(X - 4, Y + 3));
141      context.Add(new AvatarShot(X + 5, Y + 3));
142      cooldown = firingRate;
143    }
144
145
146    public void Die(EntityManager context)
147    {
148      if (!IsAlive)
149      {
150        return;
151      }
152
153      Media.PlaySound(Shooter.playerExplodeFx);
154      isAlive = false;
155      context.StartGameOver();
156      context.Add(new Explosion(X, Y));
157    }
158
159
160    public bool IsMovingLeft;
161
162    public bool IsMovingRight;
163
164    public bool IsShooting
165    {
166      get { return isShooting; }
167      set
168      {
169        isShooting = value;
170        cooldown = 0;
171      }
172    }
173
174    public bool IsAlive { get { return isAlive; } }
175
176    private bool isAlive = true;
177
178    private bool isShooting;
179
180    private int cooldown = 0;
181
182    private const double speed = 4.0;
183    private const int firingRate = 2;
184    private const double minX = 0.0;
185    private const double maxX = Shooter.pixelWidth - Shooter.spriteWidth;
186  }
187
188
189  class AvatarShot : Entity
190  {
191    public AvatarShot(double x, double y)
192    {
193      X = x;
194      Y = y;
195      Frame = 12;
196      SetHitBox(6, 6, 3, 4);
197    }
198
199    public override void Update(EntityManager context)
200    {
201      foreach (Entity o in new List<Entity>(context.Entities))
202      {
203        var enemy = o as Enemy;
204        if (enemy != null && this.Intersects(enemy))
205        {
206          enemy.Hurt(context);
207        }
208      }
209
210      Y += speed;
211
212      if (Y > Shooter.pixelHeight + Shooter.spriteHeight)
213      {
214        context.Remove(this);
215      }
216    }
217
218    private const double speed = 5.0;
219  }
220
221
222  class Enemy : Entity
223  {
224    public Enemy(double x, double y, double dx, double dy)
225    {
226      X = x;
227      Y = y;
228      DX = dx;
229      DY = dy;
230    }
231
232
233    public override void Display(Shooter shooter, int xOffset, int yOffset)
234    {
235      shooter.DrawSprite(
236        (int)X + xOffset, (int)Y + yOffset,
237        damageBlink > 1 ? blinkFrame : Frame);
238
239      if (damageBlink > 0)
240      {
241        damageBlink--;
242      }
243    }
244
245
246    public override void Update(EntityManager context)
247    {
248      // Collision detection.
249
250      // XXX: Wasteful iterating through all, could optimize by having
251      // collision groups as sublists.
252
253      // XXX: Repeating the iteration pattern from EntityManager...
254      foreach (Entity o in new List<Entity>(context.Entities))
255      {
256        var avatar = o as Avatar;
257        if (avatar != null && this.Intersects(avatar))
258        {
259          avatar.Die(context);
260        }
261      }
262
263      X += DX;
264      Y += DY;
265
266      if (HasLeftStage) {
267        context.Remove(this);
268      }
269
270      frameCount = (frameCount + 1) % (numFrames * frameDelay);
271      Frame = startFrame + (frameCount / frameDelay);
272    }
273
274
275    public void Hurt(EntityManager context)
276    {
277      if (life-- < 0)
278      {
279        Die(context);
280      }
281      if (damageBlink == 0)
282      {
283        damageBlink = 2;
284      }
285    }
286
287
288    public void Die(EntityManager context)
289    {
290      Media.PlaySound(Shooter.enemyExplodeFx);
291
292      context.Add(new Explosion(X, Y));
293      context.Remove(this);
294    }
295
296
297    bool HasLeftStage { get { return Y < -Shooter.spriteHeight; } }
298
299
300    public double DX;
301    public double DY;
302
303    private int frameCount = 0;
304
305    private int life = 10;
306
307    private int damageBlink = 0;
308
309    const int startFrame = 0;
310    const int numFrames = 8;
311    const int frameDelay = 4;
312    const int blinkFrame = 14;
313  }
314
315
316  class EntityManager
317  {
318    public EntityManager(Shooter shooter)
319    {
320      ShooterApp = shooter;
321    }
322
323
324    public void Update()
325    {
326      // Clone the entity list so the update operations can modify the
327      // original list without breaking iteration.
328      foreach (Entity e in new List<Entity>(Entities))
329      {
330        e.Update(this);
331      }
332    }
333
334
335    public void Display(Shooter shooter, int xOff, int yOff)
336    {
337      foreach (Entity e in Entities)
338      {
339        e.Display(shooter, xOff, yOff);
340      }
341    }
342
343
344    public void Add(Entity entity)
345    {
346      Entities.Add(entity);
347    }
348
349
350    public void Remove(Entity entity)
351    {
352      Entities.Remove(entity);
353    }
354
355
356    public void StartGameOver()
357    {
358      ShooterApp.StartGameOver();
359    }
360
361
362    public IList<Entity> Entities = new List<Entity>();
363
364    public Shooter ShooterApp;
365  }
366
367
368  public class Shooter : IScreen
369  {
370    public const int pixelWidth = 240;
371    public const int pixelHeight = 320;
372
373    public const int spriteWidth = 16;
374    public const int spriteHeight = 16;
375
376    public const string playerShotFx = "pew.wav";
377    public const string playerExplodeFx = "player_explode.wav";
378    public const string enemyExplodeFx = "enemy_explode.wav";
379
380
381    const string spriteTexture = "sprites.png";
382
383    EntityManager entities;
384
385    Avatar avatar = new Avatar();
386
387    bool isGameOver = false;
388
389    // How many ticks does the game keep going after game over.
390    int gameOverCounter = 40;
391
392    Random rng = new Random();
393
394    List<Vec3> starfield = new List<Vec3>();
395
396    App App;
397
398    public static void Main(string[] args)
399    {
400      var app = new TaoApp(pixelWidth, pixelHeight, "Behemoth Shooter");
401      app.RegisterService(typeof(IScreen), new Shooter());
402      app.Run();
403    }
404
405
406    public void Init()
407    {
408      Media.AddPhysFsPath("Shooter.zip");
409      // Make the zip file found from build subdir too, so that it's easy to
410      // run the exe from the project root dir.
411      Media.AddPhysFsPath("build", "Shooter.zip");
412
413      entities = new EntityManager(this);
414
415      entities.Add(avatar);
416
417      InitStarfield();
418
419      App = App.Instance;
420    }
421
422
423    public void Uninit() {}
424
425
426    void InitStarfield()
427    {
428      for (int i = 0; i < 1000; i++)
429      {
430        starfield.Add(new Vec3(rng.Next(-pixelWidth, pixelWidth), rng.Next(1000), rng.Next(100)));
431      }
432    }
433
434
435    public void StartGameOver()
436    {
437      isGameOver = true;
438    }
439
440
441    public void KeyPressed(int keycode, int keyMod, char ch)
442    {
443      switch (keycode)
444      {
445      case Sdl.SDLK_ESCAPE:
446        App.Exit();
447        break;
448      case Sdl.SDLK_LEFT:
449        avatar.IsMovingLeft = true;
450        break;
451      case Sdl.SDLK_RIGHT:
452        avatar.IsMovingRight = true;
453        break;
454      case Sdl.SDLK_SPACE:
455        avatar.IsShooting = true;
456        break;
457      }
458    }
459
460
461    public void KeyReleased(int keycode)
462    {
463      switch (keycode)
464      {
465      case Sdl.SDLK_LEFT:
466        avatar.IsMovingLeft = false;
467        break;
468      case Sdl.SDLK_RIGHT:
469        avatar.IsMovingRight = false;
470        break;
471      case Sdl.SDLK_SPACE:
472        avatar.IsShooting = false;
473        break;
474      }
475    }
476
477
478    public void Update(double timeElapsed)
479    {
480      entities.Update();
481      if (isGameOver) {
482        if (gameOverCounter-- <= 0) {
483          App.Exit();
484        }
485      }
486
487      if (rng.Next(5) == 0)
488      {
489        SpawnEnemy();
490      }
491    }
492
493
494    public void Draw(double timeElapsed)
495    {
496      Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
497      Gl.glMatrixMode(Gl.GL_MODELVIEW);
498      Gl.glLoadIdentity();
499
500      Gfx.DrawStarfield(starfield, TimeUtil.CurrentSeconds * 100,
501                        App.Service<ITaoService>().PixelScale,
502                        App.Service<ITaoService>().PixelWidth);
503
504      entities.Display(this, 0, 0);
505
506      Sdl.SDL_GL_SwapBuffers();
507    }
508
509
510    public void RandomPoint(out int x, out int y)
511    {
512      x = rng.Next(0, pixelWidth - spriteWidth);
513      y = rng.Next(0, pixelHeight - spriteHeight);
514    }
515
516
517    public void SpawnEnemy()
518    {
519      double x = pixelWidth / 2 - spriteWidth / 2;
520      x += (rng.NextDouble() - 0.5) * pixelWidth * 2.0;
521
522      double dx = 6.0 * (rng.NextDouble() - 0.5);
523      entities.Add(new Enemy(x, pixelHeight + spriteHeight, dx, -4.0));
524    }
525
526
527    public void DrawSprite(float x, float y, int frame)
528    {
529      Gfx.DrawSprite(
530        x, y, frame, spriteWidth, spriteHeight,
531        App.Service<ITaoService>().Textures[spriteTexture], 8, 8);
532    }
533  }
534}