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.