X-Git-Url: http://nitlanguage.org diff --git a/examples/shoot/src/shoot_logic.nit b/examples/shoot/src/shoot_logic.nit deleted file mode 100644 index 824af57..0000000 --- a/examples/shoot/src/shoot_logic.nit +++ /dev/null @@ -1,1077 +0,0 @@ -# This file is part of NIT ( http://www.nitlanguage.org ). -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Space shooter. -# This program is a fun game but also a good example of the scene2d module -module shoot_logic - -import scene2d - -# The ship of the player -class Player - super Sprite - - # Where the player is going - var going_target = new GoingTarget - - # Activate the `going_target` - fun goes_to(x,y: Int, speed: Int) - do - going_target.x = x - going_target.y = y - going_target.active = true - var angle = angle_to(going_target) - set_velocity(angle, speed) - end - - # Current forture of the player - var money: Int = 0 is writable - - # Number of basic bullets fired together - var nbshoots: Int = 1 is writable - - # Time bebore the player shoot again a basic bullet (cooldown) - # Shoot if 0 - var shoot_ttl = 0 - - # Number of missiles - var nbmissiles: Int = 0 is writable - - # Time bebore the player shoot again a missile (cooldown) - # Shoot if 0 - var missile_ttl = 0 - - # Remainind time when the player is protected from impacts - var protected_ttl = 0 - - # The associated play scene - # (mainly used to registed shoots) - var scene: PlayScene - - init(scene: PlayScene) do - self.scene = scene - self.width = 2400 - self.height = 2400 - end - - redef fun update - do - super - - # Out of screen? - if self.y < 0 then - self.y = 0 - self.vy = 0 - else if self.y > scene.height then - self.y = scene.height - self.vy = 0 - end - if self.x < 0 then - self.x = 0 - self.vx = 0 - else if self.x > scene.width then - self.x = scene.width - self.vx = 0 - end - - # Update of the player protection if any - if protected_ttl > 0 then protected_ttl -= 1 - - # Need to shoot basic bullets? - if shoot_ttl > 0 then - shoot_ttl -= 1 - else - shoot_ttl = 30 - for i in [0..nbshoots[ do - var shoot = new Shoot(scene) - shoot.x = x - shoot.y = top - shoot.vy = -500 - shoot.vx = (i - nbshoots / 2) * 100 - scene.player_shoots.add(shoot) - end - end - - # Need to shoot missiles? - if missile_ttl > 0 then - missile_ttl -= 1 - else if nbmissiles > 0 then - missile_ttl = 500 / nbmissiles - var shoot = new Missile(scene) - shoot.x = x - shoot.y = top - shoot.vy = -300 - shoot.vx = 0 - scene.player_shoots.add(shoot) - end - - end - - # Time before the player is respawned by the scene - var respawn_ttl: Int = 0 - - fun hit - do - if self.protected_ttl > 0 then return - self.scene.explosion(self.x, self.y, 10) - self.exists = false - - # Reset the position for respawn - self.x = scene.width / 2 - self.y = scene.height - 10000 - self.vx = 0 - self.vy = 0 - self.respawn_ttl = 50 - end - -end - -# Sprites that may be hit by the player. -# Eq. enemies, bullets, loots, etc. -class Hitable - super Sprite - - # What do do when self is hit by the player. - # By defaut, do nothing - fun hit_by_player(player: Player) do end -end - -# Destination for the player (pointer position) -class GoingTarget - super Hitable - - # true in on move, false if player is at rest - var active = false is writable - - init do - self.width = 500 - self.height = 500 - end - - redef fun hit_by_player(player) - do - if not active then return - active = false - player.vx = 0 - player.vy = 0 - end -end - -# A bullet shooted by a ship -class Shoot - super Hitable - - # Was the shoot fired by an enemy. - # Since there is no frendly fire, it is important to distinguish ownership - var enemy: Bool = false - - # The scene of the sprite - # Is used with bound limits - var scene: PlayScene - - init(scene: PlayScene) - do - self.scene = scene - self.width = 800 - self.height = 800 - end - - redef fun update - do - super - - # Out of screen ? - if self.y < -100 * 100 or self.y > scene.height + 10000 or self.x < -100 * 100 or self.x > scene.width + 10000 then - self.exists = false - end - end - - redef fun hit_by_player(player) - do - player.hit - self.exists = false - end -end - -# A advanced bullet that aims a target (player or enemy) -class Missile - super Shoot - - # The target aquired by the missile - var target: nullable Sprite = null - - # When ttl is 0 then the angle stay fixed - # The angle is updated toward the target if ttl>0 - var ttl: Int = 200 - - redef fun update - do - super - - # Do we still update the angle ? - if ttl <= 0 then return - ttl -= 1 - - # Do we have a target? - var target = self.target - if target == null or not target.exists then return - - # Just update the angle - var angle = self.angle_to(target) - self.set_velocity(angle, 300) - end -end - -# A enemy ship -# Various enemies exists, each kind has its own subclass -abstract class Enemy - super Hitable - - # The scene of the ship - # Is used to store created bullets or to get info about the player - var scene: PlayScene - - # Time bebore the enemy shoot again (cooldown) - # Shoot if 0 - # The default value is used as a grace period to avoid a first shoot on - # the first update - var shoot_ttl = 50 - - init(scene: PlayScene) - do - self.width = 2400 - self.height = 2400 - self.scene = scene - scene.enemies.add(self) - end - - redef fun update - do - super - - # Out of screen ? - if self.y > scene.height + 10000 or self.x < -100 * 100 or self.x > scene.width + 10000 then - # Note: no control on the top to let ennemies appear - self.exists = false - end - - # Need to shoot? - if shoot_ttl > 0 then - shoot_ttl -= 1 - else - shoot - end - end - - # Each enemy has its own kind of shoot strategy - # Note: is automatically called by update when shoot_ttl is expired - fun shoot do end - - # Money given when the enemy is destroyed - fun loot: Int is abstract - - # What to do when the enemy is hit by a player shoot (or by the player himself)? - # By default it kill the enemy in an explosion and generate a loot - fun hit - do - self.exists = false - scene.explosion(self.x, self.y, 5) - if 100.rand < 3 then - var upmissile = new UpMissile(scene) - upmissile.x = self.x - upmissile.y = self.y - upmissile.vx = 0 - upmissile.vy = 0 - scene.loots.add(upmissile) - scene.hitables.add(new LootArea(upmissile, 2000)) - else - for i in [0..self.loot[ do - var money = new Money(scene) - money.x = self.x - money.y = self.y - money.set_velocity(100.rand.to_f*pi/50.0, (500+self.loot).rand) - scene.loots.add(money) - scene.hitables.add(new LootArea(money, 2000)) - end - end - end - - redef fun hit_by_player(player) - do - player.hit - hit - end -end - -# Basic enemy, do not shoot -class Enemy0 - super Enemy - - redef fun loot do return 3 - - redef init(scene) - do - self.width = 3600 - self.height = 3600 - end -end - -# Simple shooter of pairs of basic bullets -class Enemy1 - super Enemy - - redef init(scene) - do - self.width = 4400 - self.height = 4400 - end - - redef fun shoot - do - # Next shoot - shoot_ttl = 50 - - # two bullets shoot each time - for dx in [-11, 11] do - var shoot = new Shoot(scene) - shoot.enemy = true - shoot.x = self.x + dx * 100 - shoot.y = self.bottom - shoot.vy = 500 - scene.enemy_shoots.add(shoot) - end - end - - redef fun loot do return 5 -end - -# Enemy that shoot missiles -class Enemy2 - super Enemy - - redef init(scene) - do - self.width = 6000 - self.height = 6000 - end - - redef fun shoot - do - # Next shoot - shoot_ttl = 200 - - # The missile targets the player - var shoot = new Missile(scene) - shoot.enemy = true - shoot.x = self.x - shoot.y = self.bottom - shoot.vy = 500 - shoot.target = scene.player - scene.enemy_shoots.add(shoot) - end - - redef fun loot do return 10 -end - -# Enemy that shoot rings of basic bullets -class Enemy3 - super Enemy - - redef init(scene) - do - self.width = 5800 - self.height = 5800 - end - - redef fun shoot - do - # Next shoot - shoot_ttl = 50 - - for i in [0..10[ do - var shoot = new Shoot(scene) - shoot.enemy = true - shoot.x = self.x - shoot.y = self.bottom - shoot.set_velocity(pi/5.0*i.to_f, 500) - scene.enemy_shoots.add(shoot) - end - end - - redef fun loot do return 20 -end - -# Enemy with a turret that shoot burst of bullets toward the player -class Enemy4 - super Enemy - - # The angle of the turret - var angle: Float = 0.0 - - redef init(scene) - do - self.width = 4200 - self.height = 4200 - end - - redef fun update - do - super - - # Rotate the turret toward the player - var target = scene.player - if target.exists then - angle = self.angle_to(target) - end - end - - # Shoots come in burst - var nbshoots: Int = 0 - - redef fun shoot - do - # Next shoot: is there still bullets in the burst? - if self.nbshoots < 10 then - # Is ther - self.nbshoots += 1 - shoot_ttl = 5 - else - self.nbshoots = 0 - shoot_ttl = 80 - end - - # Shoot with the turret angle - var shoot = new Shoot(scene) - shoot.enemy = true - shoot.x = self.x - shoot.y = self.y - shoot.set_velocity(angle, 500) - scene.enemy_shoots.add(shoot) - end - - redef fun loot do return 20 -end - -# Enemy that rush directly on the player -class EnemyKamikaze - super Enemy - - redef init(scene) - do - self.width = 3200 - self.height = 3200 - end - - redef fun update - do - super - - # Try to target the player - var target = scene.player - if not target.exists then return - - var angle = self.angle_to(target) - self.set_velocity(angle, 600) - end - - redef fun loot do return 5 -end - -# The boss has two semi-independent arms -class Boss - super Enemy - - # Left arm - var left_part: BossPart - - # Right arm - var right_part: BossPart - - init(scene) - do - super - self.width = 140 * 100 - self.height = 96 * 100 - self.x = scene.width / 2 - self.y = -100 * 100 - self.left_part = new BossPart(self, -66*100) - self.right_part = new BossPart(self, 66*100) - end - - var flick_ttl: Int = 0 - - redef fun update - do - if flick_ttl > 0 then flick_ttl -= 1 - - # Path of the boss (down then left<->right) - if self.y < 20000 then - self.vx = 0 - self.vy = 100 - else if self.vx == 0 then - self.vx = 100 - self.vy = 0 - else if self.x > scene.width - 10000 and self.vx > 0 then - self.vx = -self.vx - else if self.x < 10000 and self.vx < 0 then - self.vx = -self.vx - end - - super - end - - redef fun shoot - do - # Do not shoot if not ready - if self.vy != 0 then return - - # Try to target the player - var target = scene.player - if not target.exists then return - - # Next shoot: burst if no arms remains - if left_part.exists or right_part.exists then - shoot_ttl = 60 - else - shoot_ttl = 20 - end - - # Shoot the player with a basic bullet - var shoot = new Shoot(scene) - shoot.enemy = true - shoot.x = self.x - shoot.y = self.bottom - var angle = shoot.angle_to(target) - shoot.set_velocity(angle, 500) - scene.enemy_shoots.add(shoot) - end - - redef fun loot do return 100 - - var live: Int = 20 - - redef fun hit - do - # Protected while an arm remains - if left_part.exists or right_part.exists then return - - if live > 0 then - live -= 1 - flick_ttl = 2 - else - super - scene.explosion(self.x, self.y, 30) - end - end -end - -# An arm of a boss -class BossPart - super Enemy - - # The associated boss - var boss: Boss - - # Relative x coordonate (center to center) of the arm - var relx: Int - - # Relative y coordonate (center to center) of the arm - var rely: Int = 36 * 100 - - var live: Int = 10 - - init(boss: Boss, relx: Int) - do - self.boss = boss - self.relx = relx - super(boss.scene) - self.width = 38 * 100 - self.height = 48 * 100 - - # Alternate the shoots of the arms - if relx > 0 then - shoot_ttl += 300 - end - self.x = boss.x + relx - self.y = boss.y + rely - end - - redef fun update - do - self.x = boss.x + relx - self.y = boss.y + rely - - super - - if flick_ttl > 0 then flick_ttl -= 1 - end - - redef fun shoot - do - # Do not shoot if not ready - if self.boss.vy != 0 then return - - # Next shoot - shoot_ttl = 600 - - # Shoot a missile that targets the player - var shoot = new Missile(scene) - shoot.enemy = true - shoot.x = self.x - shoot.y = self.bottom - shoot.vy = 500 - shoot.target = scene.player - scene.enemy_shoots.add(shoot) - end - - var flick_ttl: Int = 0 - - redef fun hit - do - if live > 0 then - live -= 1 - flick_ttl = 2 - else - super - end - end - - redef fun loot do return 10 -end - -# Whatever reward or bonus that can be picked by the player -abstract class Loot - super Hitable - - var scene: PlayScene - - init(scene: PlayScene) - do - self.scene = scene - self.width = 400 - self.height = 400 - end - - # Magnet effect: The loot will move to the target if set - # See LootArea for details - var target: nullable Sprite = null - - redef fun update - do - super - - # Out of screen ? - if self.y > scene.height + 10000 then - self.exists = false - end - - var target = self.target - if target == null then - # Not magneted: deploy - - # Heavy fuild friction to stops the explosion - # Loots are placed with a explosion, see `Enemy::hit' - self.vx = self.vx*7/8 - self.vy = self.vy*7/8 - - # Background scroling - self.y += 50 - - else if target.exists then - # Magneted: rush toward the target - var angle = self.angle_to(target) - self.set_velocity(angle, 800) - - else - # Magneted but dead target: reset the loot - self.vx = 0 - self.vy = 0 - self.target = null - end - end -end - -# Basic money loot -class Money - super Loot - - redef fun hit_by_player(player) - do - self.exists = false - player.money += 1 - if player.money > 100 then - player.money -= 100 - player.nbshoots += 1 - end - end -end - -# Increase the number of missiles -class UpMissile - super Loot - - redef fun hit_by_player(player) - do - self.exists = false - player.nbmissiles += 1 - end -end - -# A loot area is an invisible field used to implement the magnet effets of loots -# The principle is: -# * the loot is an invisible sprite with a hitbox larger than the loot hitbox -# * the lootbox remains centered on the loot -# * when the player hit the lootarea, then the loot is set to target the player -# * when the player hit the loot, then the player gains effectively the loot -class LootArea - super Hitable - - # The associated loot - var loot: Loot - - init(loot: Loot, radius: Int) - do - self.loot = loot - self.width = radius * 2 + loot.width - self.height = radius * 2 + loot.height - end - - redef fun update - do - # position remains centered on the loot - self.x = loot.x - self.y = loot.y - - # No area if no loot - if not loot.exists then self.exists = false - - # the super is useless but it is a good practice to call it - super - end - - redef fun hit_by_player(player) - do - # Kill the area - self.exists = false - - # The loot now targets the player - loot.target = player - end -end - -# A non interactive element of an explosion -# A real explosion is made of many Explosion object -# Use the `PlayScene::explosion` method to generate a full explosion -class Explosion - super Sprite - - # Time before the sprite vanishes - var ttl: Int = 10 - - redef fun update - do - # Heavy fuild friction to stops the explosion - self.vx = self.vx*7/8 - self.vy = self.vy*7/8 - - # Background scrolling - self.y += 50 - - super - - # Vanishes? - if ttl > 0 then - ttl -= 1 - else - exists = false - end - end -end - -# A star is a non-interactive background element -# Stars are used to simulate a continuous global scroling -class Star - super Sprite - - # The scene of the sprite - # Is used with bound limits - var scene: ShotScene - - init(scene: ShotScene) - do - self.scene = scene - # Randomely places stars on the plane - self.x = scene.width.rand - self.y = scene.height.rand - self.vy = 40.rand + 11 - end - - redef fun update - do - super - - # Replace the star on the top - if self.y > scene.height then - self.y = 200.rand * -100 - self.x = scene.width.rand - self.vy = 40.rand + 11 - end - end -end - -class ShotScene - super Scene - - # When a scene need to be replaced, just assign the next_scene to a non null value - var next_scene: nullable ShotScene = null is writable - - # The width of the whole scene - var width: Int is writable - - # The height of the whole scene - var height: Int is writable - - init(w,h: Int) - do - width = w - height = h - end -end - -# The main play state -class PlayScene - super ShotScene - - # The player ship - var player: Player - - # Shoots of the player - var player_shoots = new LiveGroup[Shoot] - - # Enemy ships - var enemies = new LiveGroup[Enemy] - - # Soots of the enemy - var enemy_shoots = new LiveGroup[Shoot] - - # Collectible loots - var loots = new LiveGroup[Loot] - - # Non active stuff like explosions - var pasive_stuff = new LiveGroup[LiveObject] - - # Background stuff like stars - var background = new LiveGroup[LiveObject] - - # All other hitable sprites - var hitables = new LiveGroup[Hitable] - - # All sprites - var sprites = new LiveGroup[LiveObject] - - init(w,h) - do - super - self.player = new Player(self) - player.x = self.width / 2 - player.y = self.height - 10000 - self.sprites.add(background) - self.sprites.add(pasive_stuff) - self.sprites.add(loots) - self.sprites.add(player_shoots) - self.sprites.add(enemy_shoots) - self.sprites.add(enemies) - self.sprites.add(self.player) - self.sprites.add(hitables) - - for i in [0..100[ do - background.add(new Star(self)) - end - - hitables.add(player.going_target) - end - - # Generate an explosion - fun explosion(x, y: Int, radius: Int) - do - # Project explosion parts from the given position - # The strong friction and the short ttl of each part will achieve the effect - for i in [0..radius[ do - var ex = new Explosion - ex.x = x - ex.y = y - ex.set_velocity(100.rand.to_f*pi/50.0, (50*radius).rand) - ex.ttl += radius.rand - pasive_stuff.add(ex) - end - end - - var enemy_remains: Int = 15 - var boss_wait_ttl: Int = 0 - var boss: nullable Boss - - redef fun update - do - sprites.gc - sprites.update - - if enemy_remains == 0 then - if boss_wait_ttl > 0 then - boss_wait_ttl -= 1 - else if boss == null then - boss = new Boss(self) - enemy_remains = 15 - else if not boss.exists then - boss = null - end - else if 100.rand < 1 then - enemy_remains -= 1 - if enemy_remains == 0 then - boss_wait_ttl = 500 - end - var rnd = 100.rand - var enemy: Enemy - if rnd < 40 then - enemy = new Enemy0(self) - else if rnd < 60 then - enemy = new Enemy1(self) - else if rnd < 70 then - enemy = new EnemyKamikaze(self) - else if rnd < 90 then - enemy = new Enemy2(self) - else if rnd < 95 then - enemy = new Enemy3(self) - else - enemy = new Enemy4(self) - end - enemy.x = (self.width - 20000).rand + 10000 - enemy.vy = 200.rand + 100 - if 10.rand < 3 then - enemy.vx = 200.rand - 100 - end - end - - for ps in player_shoots do - if not ps.exists then continue - var target: nullable Enemy = null - var td = 100000 # big int - for e in enemies do - if not e.exists then continue - if ps.overlaps(e) then - ps.exists = false - e.hit - end - var d = (e.x - ps.x).abs + (e.y - ps.y).abs - if td > d then - target = e - td = d - end - end - if ps isa Missile and (ps.target == null or not ps.target.exists) then - ps.target = target - end - end - - for e in enemies do - if not e.exists then continue - if player.exists and player.overlaps(e) then - e.hit_by_player(player) - end - end - for s in enemy_shoots do - if not s.exists then continue - if player.exists and player.overlaps(s) then - s.hit_by_player(player) - end - end - for l in loots do - if not l.exists then continue - if player.exists and player.overlaps(l) then - l.hit_by_player(player) - end - end - for l in hitables do - if not l.exists then continue - if player.exists and player.overlaps(l) then - l.hit_by_player(player) - end - end - if not player.exists then - if player.respawn_ttl > 0 then - player.respawn_ttl -= 1 - else - player.exists = true - player.protected_ttl = 100 - self.sprites.add(self.player) - end - end - end -end - -### - -class MenuScene - super ShotScene - - var sprites = new LiveGroup[LiveObject] - - init(w,h) - do - super - for i in [0..100[ do - sprites.add(new Star(self)) - end - end - - var play: Bool = false is writable - var ttl: Int = 50 - - redef fun update - do - sprites.update - - if not play then return - if ttl > 0 then - ttl -= 1 - return - end - next_scene = new PlayScene(width,height) - end -end - -fun headless_run -do - srand_from 0 - print "Headless run" - # Only run the playscene - var scene = new PlayScene(80000,60000) - # beefup the player - scene.player.nbshoots = 5 - scene.player.nbmissiles = 5 - # play - print "Play" - var turns = 10 - if args.length > 0 then - turns = args.first.to_i - end - for i in [0..turns[ do - for j in [0..10000[ do - scene.update - end - print "{i}: money={scene.player.money} enemies={scene.enemies.length} shoots={scene.player_shoots.length}" - end - print "Game Over" -end - -headless_run