PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/examples/Shooter/Shooter.cs

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