Home > Enterprise >  Nil class for objects in a collision detection test
Nil class for objects in a collision detection test

Time:11-29

I ran into a problem while testing for a collision detection system. For the sake of visualization, you can think of the overall structure of the code as Main Class =>Class(ship, shell, enemy), the shell objects are initialized in Ship(to take ship coordinate), the enemy objects are initialized in the update method which belongs to Main class and the collision test is in the Enemy class (to take the coordinates of enemy objects). I have made sure the shell object can be accessed by other classes through attr_accessor but the error: undefined method shell_hit_box' for nil:NilClass ` which pointed to the collision detection method still occured:

def penetration?(shell)
 shell.shell_hit_box && [[shell.shell_hit_box, shell.shell_hit_box], [shell.shell_hit_box,                        shell.shell_hit_box],
 [shell.shell_hit_box.x3, shell.shell_hit_box], [shell.shell_hit_box, shell.shell_hit_box]].any?      do |coordinates|
 @box.contains?(coordinates[0], coordinates[1])
  puts"Shikkikan, penetration on enemy confirmed!" 
            end
end

My assumption is that shell objects were not passed into the method correctly from the base initialization spot:

  

 def fire_attilery(shell) #this is where the shell is created and pushed into an array to store
            x = @sprite.x   (@sprite.width)*0.5   @x_velocity
            y [email protected]   (@sprite.height)*0.5   @y_velocity 
            shell=Shell.new(x, y, @sprite.rotate)
            @magazine << shell
   end

Method to give the shell momentum:

  def move
                @sprite.x   = @x_velocity
                @sprite.y  = @y_velocity
                @magazine.each do |shell| # shell is push out of array and ordered to fly foward
                shell.shell_fired
        end

Detecting collision in update method from Main Class

 def update #updating player movement and and spawning enemy
        @bismark.move
        @bismark.deaccelare
        @enemy= Enemy.new
        @fleet << @enemy
        @enemy.moving_slowly
        @fleet.each { |enemy| enemy.move }
            if @enemy.penetration?(shell) #calling the colision test method in Enemy Class
                    print "Working"
            end
    end

That should be all the method that has an organic relationship with each other and related to the error, I hope this could be resolved.

This is the full code for sample if you think the error is not from what I thought and wrote:

require 'ruby2d'

######################################################################################
class Ship
    attr_accessor :skin, :x , :y, :shell #making the shell object accessible?
        def initialize(skin, x, y)
            @skin = skin
            @x = x
            @y = y
            @magazine = []
            @fire_rate = 0
            @remaning_ammo = 7
            @x_velocity= 0
            @y_velocity = 0
            @sprite = Sprite.new(skin,
                width: 78,
                height: 99,
                clip_width: 220,
                time: 150,
                rotate: 180,
                animations: {
                  floating: 0..1,
                  open_fire:2..3 ,
                }
              )
        end

        def quarter_speed
            @sprite.play(animation: :floating, loop:true )
        end

        def fire
            @sprite.play(animation: :open_fire, loop:false)
        end

        def rotation(angle)
            case angle
                when :port
                     @sprite.rotate -=2
                when :starboard
                    @sprite.rotate  =2
            end
        end

        def acceleration(angle)
            case angle
                when :forwards  
                    @x_velocity = Math.sin(@sprite.rotate*Math::PI/180) 
                    @y_velocity -= Math.cos(@sprite.rotate*Math::PI/180) 
                when :backwards  
                    @x_velocity-= Math.sin(@sprite.rotate*Math::PI/180) 
                    @y_velocity  = Math.cos(@sprite.rotate*Math::PI/180) 
            end
        end

        def deaccelare
            @x_velocity*= 0.99
            @y_velocity*= 0.99
        end

        def move
                @sprite.x   = @x_velocity
                @sprite.y  = @y_velocity
                @magazine.each do |shell| # shell is push out of array and ordered to fly foward
                shell.shell_fired
        end

        end

       def fire_attilery(shell) #this is where the shell in created and push into an array to store
            if @fire_rate 40 < Window.frames
                x = @sprite.x   (@sprite.width)*0.5   @x_velocity
                y [email protected]   (@sprite.height)*0.5   @y_velocity 
                shell=Shell.new(x, y, @sprite.rotate)
                @magazine << shell
                @fire_rate = Window.frames
                
            end

       end

end


class  Shell
    attr_reader :shell_hit_box #making the hit box accessible?
        def initialize(x, y, rotation)
            @shell = Sprite.new('images/pistol_shell.png',
            x: x,
            y: y,
            width:13,
            height:20,
            rotate: rotation)
            @shell_count = 7
            @x_velocity= Math.sin(@shell.rotate*Math::PI/180) 
            @y_velocity = -Math.cos(@shell.rotate*Math::PI/180) 
            @shell_hit_box = Square.new(x: x, y: y, size: 10, color:[1,1,1,0.001])
        end 
        

        def shell_fired
            @shell.x  = @x_velocity*22
            @shell.y  = @y_velocity*22
            @shell_hit_box.x  = @x_velocity*22
            @shell_hit_box.y  = @y_velocity*22

        end
end


class Enemy
    attr_accessor 
        def initialize
            @x_velocity= 0
            @y_velocity =0
            @baseship = Sprite.new('images/enemy.png',
                x: rand(Window.width),
                y:rand(Window.height),
                width: 80,
                height: 100,
                clip_width: 250,
                time: 300,
                rotate: rand(360),
                animations: {
                  shipenemy: 0..3,})
                  @x_velocity = Math.sin(@baseship.rotate*Math::PI/180) 
                  @y_velocity -= Math.cos(@baseship.rotate*Math::PI/180) 
                  @a = @baseship.x   (@baseship.width)*0.5
                  @b = @baseship.y   (@baseship.height)*0.5
                  @box = Square.new(x: @a, y: @b, size:30, color:[1, 0, 1, 0.001] )

        end

        def moving_slowly
            @baseship.play(animation: :shipenemy, loop:true )
        end

        def move
                @box.x  = @x_velocity
                @box.y  = @y_velocity
                @baseship.x   = @x_velocity
                @baseship.y  = @y_velocity
        end

        def penetration?(shell) #this is detecting the collision , source of error i think
            shell.shell_hit_box && [[shell.shell_hit_box, shell.shell_hit_box], [shell.shell_hit_box, shell.shell_hit_box],
            [shell.shell_hit_box.x3, shell.shell_hit_box], [shell.shell_hit_box, shell.shell_hit_box]].any? do |coordinates|
            @box.contains?(coordinates[0], coordinates[1])
              puts"Sir, penetration on enemy confirmed!" 
                                                                                 end
        end
    end



class Mainscreen 

    def initialize
        @bismark = Ship.new('images/bismark',230, 230)
        @bismark.quarter_speed
        @fleet = []
    end
    
    def ship_turn(angle)
        @bismark.rotation(angle)
    end

    def bismark_acceleration(angle)
        @bismark.acceleration(angle)
    end
   
    def update #updating player movement and and spawning enemy
        @bismark.move
        @bismark.deaccelare
        @enemy= Enemy.new
        @fleet << @enemy
        @enemy.moving_slowly
        @fleet.each { |enemy| enemy.move }
            if @enemy.penetration?(shell) #calling the colision test method in Enemy Class
                    print "Working"
            end
    end

    def bismark_fire_shell
        @bismark.fire_attilery
        @bismark.fire
    end

end

mainscreen = Mainscreen.new

update do
    mainscreen.update
end

################################The code below doesnt matter much, just user input for movement##########################
on :key_held do |event|
    case event.key
        when 'up'
            mainscreen.bismark_acceleration(:forwards)
        when 'down'
            mainscreen.bismark_acceleration(:backwards)
        when 'left'
            mainscreen.ship_turn(:port)
        when 'right'
            mainscreen.ship_turn(:starboard)
        end
    end

on :key_down do |event|
    if event.key == 'f'
    mainscreen.bismark_fire_shell
    artilery_sound.play
    end
end

show 




CodePudding user response:

I'm not going to even try to debug your application, but if you want to know if shell is nil just raise an exception when that happens. As just one example for debugging purposes:

def penetration? shell
  raise if shell.nil?
  # ... whatever
end

You couldn't even call #fire_artillery or #penetration? unless you were passing an argument for shell, but that argument could still be nil. Since there are a lot of places where you need to evaluate whether shell can respond_to? :shell_hit_box, ideally you should fix the core issue wherever your collection of Shell objects are stored. For example, unless you can fill a @magazine with duds that don't fire, you might be better off converting the instance variable to a method that ensures that there are no nil objects in the magazine.

If your goal is just to debug this, then you can use Ruby's standard debug gem to trace your shell variable and break whenever shell == nil, or use raise judiciously to explore the backtrace and find out who the callers are and why they're calling methods with nil as the argument for shell.

  • Related