Contains one or more values which are not numbers (factory.create and go.delete)(SOLVED!)

Functions with trigger collisions
enemy (moving to the player)
projectile (moving to the enemy with damage, enemy gets damage and then gets deleted when hp = 0 then error goes off) turned of the go.delete and no errors.

Errors:

ERROR:SCRIPT: scripts/castle_player.script:34: argument #-1 contains one or more values which are not numbers: vmath.vector3(nan, nan, nan)
stack traceback:
  [C]:-1: in function create
  scripts/castle_player.script:34: in function spawn_projectile
  scripts/castle_player.script:57: in function <scripts/castle_player.script:52>

Quick find for the errors:
player script 34 = factory.create(“/factory_parent#projectile_factory”, go.get_position(), nil,
scripts 57 on castle_player (function update) = spawn_projectile(self)
script 52 = function update

turning of the go.delete() in enemy_units.script
local function change_hp(self, amount) shows no errors but game object exists with 0 hp.

castle_player:

function init(self)
	self.projectile_int = 1.5
	self.projectile_time_spent = 0

	self.explode_int = 2.5
	self.explode_time_spent = 0
	
	self.enemies_in_range = {}

	self.hp = 100
	self.max_hp = 100
end

local function get_closest_enemy(self)
	local closest = nil
	local min_dist = 999999
	
	for _, enemyu_coll in pairs(self.enemies_in_range) do
		if go.exists(enemyu_coll) then
			local dist = vmath.length(go.get_position(enemyu_coll) - go.get_position())
			if dist < min_dist then
				min_dist = dist
				closest = enemyu_coll
			end
		end
	end
	return closest
end

local function spawn_projectile(self)
	local closest = get_closest_enemy(self)
	local dir = vmath.normalize(go.get_position(closest) - go.get_position())
	
	factory.create("/factory_parent#projectile_factory", go.get_position(), nil,
	{
		direction = dir
	}
	, 1)
end

local function spawn_explode(self)
	local closest = get_closest_enemy(self)
	--local dir = vmath.normalize(go.get_position(closest) - go.get_position())

	factory.create("/factory_parent#explode_factory", go.get_position(), nil,
	{
		goal_point = go.get_position(closest)
	}
	, 1)
end

function update(self, dt)
	
	self.projectile_time_spent = self.projectile_time_spent + dt

	if self.projectile_time_spent >= self.projectile_int and #self.enemies_in_range > 0 then
		spawn_projectile(self)
		self.projectile_time_spent = 0
	end
	
	self.explode_time_spent = self.explode_time_spent + dt

	if self.explode_time_spent >= self.explode_int and #self.enemies_in_range > 0 then
		spawn_explode(self)
		self.explode_time_spent = 0
	end
end

local function change_hp(self, amount)
	self.hp = self.hp + amount
	print("current castle hp: ", self.hp)
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") then
		if message.other_group == hash("enemyu_coll") then
			table.insert(self.enemies_in_range, message.other_id)
		end
	end
	if message_id == hash("change_hp") and message.change_amount then
		change_hp(self, message.change_amount)
	end
	
end

enemy_units:

go.property("enemy_type", hash(""))

-- maybe need this bellow!
--local game_manager = require "scripts.game_manager"

local stats = {
	torch_goblin = {
		speed = 100,
		hp = 25,
		damage = 11,
	},
	tnt_goblin = {
		speed = 100,
		hp = 14,
		damage = 24,
	},
	knight_red = {
		speed = 34,
		hp = 41,
		damage = 20,
	}
}

-- Shared value between all instances of this script (local hp = 0, local damage = 0, local speed = 0)

function init(self)
	local type = ""
	if self.enemy_type == hash("torch_goblin") then type = "torch_goblin"
	elseif self.enemy_type == hash("tnt_goblin") then type = "tnt_goblin"
	elseif self.enemy_type == hash("knight_red") then type = "knight_red" end
	
	self. hp = stats[type].hp
	self.damage = stats[type].damage
	self.speed = stats[type].speed
	
	self.castle_pos = go.get_position("/castle_player")
	self.direction = vmath.normalize(self.castle_pos - go.get_position())
	
	--[[ local angle = vmath.quat_rotation_z(math.atan2(self.direction.y, self.direction.x))
	go.set_rotation(angle)
	-- ]]
	sprite.set_hflip("#sprite", self.direction.x < 0)
end

function final(self)
	
end

function update(self, dt)
	local pos = go.get_position()
	local new_pos = pos + self.direction * self.speed * dt
	go.set_position(new_pos)
	
end

local function change_hp(self, amount)
	self.hp = self.hp + amount
	if self.hp <= 0  then
		factory.create("/factory_parent#burst_factory", go.get_position(), nil, nil, 1)
		-- with the delete line bellow enable it will cause error???!!!!
		go.delete()
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") then
		if message.other_group == hash("castlep_coll") then
			msg.post(message.other_id, "change_hp", {change_amount = -self.damage})
			factory.create("/factory_parent#burst_factory", go.get_position(), nil, nil, 1)
			go.delete()
		--else message.other_group = hash("firerange_coll")
		--print("Fire Now!!!")
		-- game_manager.print_table(message)
		-- print("I am Deleting Myself")
		end
	end
	if message_id == hash("change_hp") and message.hp_change ~= nil then
		change_hp(self, message.hp_change)
	end
end

projectile:

go.property("direction", vmath.vector3())


function init(self)
	self.speed = 240
	self.time_alive = 0
	self.damage = 25
	
	local angle = vmath.quat_rotation_z(math.atan2(self.direction.y, self.direction.x))
	go.set_rotation(angle)
end


function update(self, dt)
	local pos = go.get_position()
	local new_pos = pos + self.speed * self.direction * dt
	go.set_position(new_pos)

	self.time_alive = self.time_alive + dt
	if self.time_alive > 4 then go.delete() end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") and message.other_group == hash("enemyu_coll") then
		msg.post(message.other_id, "change_hp", {hp_change = -self.damage})
		go.delete()
	end
end

1 Like

It is not go.delete() itself being a root cause of the error, but it exposes the stale target stored in the table - probably, becayse the deleted enemy remains in self.enemies_in_range. The table is therefore not empty, but get_closest_enemy() can still return nil.

Handle both trigger entry and exit:

if message_id == hash("trigger_response")
and message.other_group == hash("enemyu_coll") then
    if message.enter then
        self.enemies_in_range[message.other_id] = true
    else
        self.enemies_in_range[message.other_id] = nil
    end
end

You can also validate the target and direction before spawning:

local closest = get_closest_enemy(self)
if not closest then
    return
end

local delta = go.get_position(closest) - go.get_position()
if vmath.length_sqr(delta) < 0.000001 then
    return
end

local dir = vmath.normalize(delta)

Then use the enemies table as a set and iterate with:

for enemy_id in pairs(self.enemies_in_range) do
1 Like

It is not go.delete() itself being a root cause of the error, but it exposes the stale target stored in the table - probably, becayse the deleted enemy remains in self.enemies_in_range. The table is therefore not empty, but get_closest_enemy() can still return nil.

So the deleted game object table is still around/stored, even though the game object is deleted. That gives the error. Then I guess the error should also appear when the game object gets deleted on trigger castle_coll, which it doesn’t.

enemy_units.script

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") then
		if message.other_group == hash("castlep_coll") then
			msg.post(message.other_id, "change_hp", {change_amount = -self.damage})
			factory.create("/factory_parent#burst_factory", go.get_position(), nil, nil, 1)
			go.delete()
		end
	end

figuring also where to put this code( Handle both trigger entry and exit:). As putting it on_message gives new errors.

ERROR:SCRIPT: scripts/castle_player.script:19: bad argument #1 to 'exists' (url expected, got boolean)
stack traceback:
  [C]:-1: in function exists
  scripts/castle_player.script:19: in function get_closest_enemy
  scripts/castle_player.script:31: in function spawn_projectile
  scripts/castle_player.script:57: in function <scripts/castle_player.script:52>

Deleted gameobject lives until the end of the frame, they are truly deleted before begining of next frame.
Maybe this is your problem

Found new discovery, target table stored error might be the explode script. which stores also targets closest and 90% sure its not deleting it or something.

  • Disabling the (explode) or (projectile) spawn(hit then kill the enemy), I can kill 1000 enemies with explode/projectile function spawn and go.delete, without the error. Enable both will give the errors to the arrow spawn function.
  • Both stores the target table, guessing when target is remove from either one, the other still has it stored(still checking). but then I have this script to remove nil stored data.

game_manager script:

local m = {
	current_level = "level_one_proxy",
	player_stats = {
		is_barrel_unlocked = false,
		arrowup_damage = 10,
		arrowup_interval = 2,
		barrelup_damage = 28,
		barrelup_interval = 4,
		hpup_regen = 2,
		maxup_hp = 100,
		hp = 100,
		fireup_range = 1300,
		gold_amount = 100,
	},
	skill_init = {
		arrowup_damage = 1,
		arrowup_interval = 1,
		barrelup_damage = 1,
		barrelup_interval = 1,
		hpup_regen = 1,
		maxup_hp = 1,
		fireup_range = 1,
	},
	skill_effects = {
		arrowup_damage = 1.2,
		arrowup_interval = .9,
		barrelup_damage = 1.15,
		barrelup_interval = 0.85,
		hpup_regen = 1.4,
		maxup_hp = 1.15,
		fireup_range = 1.1,
	},
	skill_cost = {
		arrowup_damage = 5,
		arrowup_interval = 7,
		barrelup_damage = 6,
		barrelup_interval = 8,
		hpup_regen = 6,
		maxup_hp = 9,
		fireup_range = 5,
		unlockup_barrel = 20
	},
	skill_cost_base = {
		arrowup_damage = 5,
		arrowup_interval = 7,
		barrelup_damage = 6,
		barrelup_interval = 8,
		hpup_regen = 6,
		maxup_hp = 9,
		fireup_range = 5,
	}
}

function m.print_table(table)
	-- print("Print Table Is Called!")
	for key, value in pairs(table) do
		print(key, " : ", value)
	end
end

function m.remove_value_from_table(table, value)
	for key, cur_value in pairs(table) do
		if cur_value == value and table[key] ~= nil then
			table.remove(table, key)
		end
	end
end

return m

explode script:

go.property("goal_point", vmath.vector3())
local game_manager = require "scripts.game_manager"

function init(self)
	self.speed = 140
	self.damage_amount = 25
	self.direction = vmath.normalize(self.goal_point - go.get_position())
	print(self.direction)

	self.enemies_in_range = {}

	local duration = vmath.length(self.goal_point - go.get_position()) / self.speed
	
	go.animate("#sprite", "scale", go.PLAYBACK_ONCE_PINGPONG, vmath.vector3(1.2), go.EASING_LINEAR, duration, 0, nil)
end

local function explode(self)
	for _, enemyu_coll in pairs(self.enemies_in_range) do
		if go.exists(enemyu_coll) then
			msg.post(enemyu_coll, "change_hp", {hp_change = -self.damage_amount})
		end
	end
	factory.create("/factory_parent#explosion_factory", go.get_position(), nil, nil, 1.2)
	go.delete()
end

function update(self, dt)
	local pos = go.get_position()
	local new_pos = pos + self.speed * self.direction * dt
	go.set_position(new_pos)

	if vmath.length(self.goal_point - new_pos) < 5 then
		explode(self)
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") and message.other_group == hash("enemyu_coll") then
		if message.enter then
			table.insert(self.enemies_in_range, message.other_id)
		elseif self.enemies_in_range[message.other_id] then
			game_manager.remove_value_from_table(self.enemies_in_range, message.other_id)
		end
	end
end

ERROR:SCRIPT: scripts/castle_player.script:34: argument #-1 contains one or more values which are not numbers: vmath.vector3(nan, nan, nan)

this is likely to be created in the

	local dir = vmath.normalize(go.get_position(closest) - go.get_position())

if closest is nil then defold will (probably) determines go.get_position(nil) as the current object. so the function becomes go.get_position() - go.get_position() this will generate a vmath.vector3 = (0,0,0), causing a divide by zero error in the normalise function. defold must not like a nan,nan,nan passed as a property in the factory create

I see. I don’t know why then that get_closest_enemy and remove_value_from_table aren’t playing together since they work perfectly fine if one or the other is not being used(disable bullet from spawning and killing enemy). Return closest and table.remove(table,key).

I’ll try to change the nil value in the get_closest_enemy function and test what works(idk).

local function get_closest_enemy(self)
	local closest = nil
	local min_dist = 999999
	
	for _, enemyu_coll in pairs(self.enemies_in_range) do
		if go.exists(enemyu_coll) then
			local dist = vmath.length(go.get_position(enemyu_coll) - go.get_position())
			if dist < min_dist then
				min_dist = dist
				closest = enemyu_coll
			end
		end
	end
	return closest
end
function m.remove_value_from_table(table, value)
	for key, cur_value in pairs(table) do
		if cur_value == value and table[key] ~= nil then
			table.remove(table, key)
		end
	end
end

in your explode script you have

function on_message(self, message_id, message, sender)
	if message_id == hash("trigger_response") and message.other_group == hash("enemyu_coll") then
		if message.enter then
			table.insert(self.enemies_in_range, message.other_id)
		elseif self.enemies_in_range[message.other_id] then
			game_manager.remove_value_from_table(self.enemies_in_range, message.other_id)
		end
	end
end

table.insert will insert a new index with the message.other.id as the stored value.

Then you have “self.enemies_in_range[message.other_id]”. here you testing the KEY/index not the value. So it will not find an enemy as it checking the hashed id against the index .

This is cleaner, but you are using the id as the key not the value

if message.enter==true then
        -- add item using id as key and value
		self.enemies_in_range[message.other_id] = message.other_id 
else
  --no point testing. let lua worry about it. it would searching once for the if anyway and then again to set it to nil
  self.enemies_in_range[message.other_id] = nil
  end 

You still need a way to notify the castle that the enemies have been deleted. The above will clear them if they move out of range. I ran a quick sample project to check if deleting an enemy within the trigger collision object sends a trigger response message with message.enter set to false, but it didn’t. The easiest (though not necessarily the best) approach might be to clear them during your closest check. so using the above where the KEY is the enemy id

for key, enemyu_coll in pairs(self.enemies_in_range) do
		if go.exists(enemyu_coll) then
			local dist = vmath.length_sqr(go.get_position(enemyu_coll) - go.get_position())
			if dist < min_dist then
				min_dist = dist
				closest = enemyu_coll
			end
		else
            -- clear key out of table
            self.enemies_in_range[key] = nil
	end

then in your spawn function will need to check if closest is nil and then only proceed if its not nil.

There are a few tweaks you can do the closest enemy function.
Firstly, you could pass the function the castle position. This will stop multiple go.get_position calls. Assuming the castle doesn’t move you could actually store the position in the init function . ie self.position = go.get_position() and pass this.

Secondly, as you are using length as a comparative only. vmath.length_sqr is a better function as it removes needless square root computation on every call.

I hope that makes sense

1 Like

Kinda make sense to me, but Ill try it! As for the castle position, its already stored in the init on enemy_units.script(castle not moving), so when the enemies spawn, it will immediately get the castle position. I’'ll use vmath.length_sqr for all my 2D games now, vmath.length for 3D(if what I understand is unnecessary curving that comes along with vmath.length calculation). Getting new errors with the code inserted on

castle_player.script
function on message
local function get_closest_enemy

Will do more testing.

ERROR:SCRIPT: scripts/castle_player.script:40: argument #-1 contains one or more values which are not numbers: vmath.vector3(nan, nan, nan)
stack traceback:
  [C]:-1: in function create
  scripts/castle_player.script:40: in function spawn_projectile
  scripts/castle_player.script:63: in function <scripts/castle_player.script:58>
local game_manager = require("scripts.game_manager")

function init(self)
	-- had to discard some code, using game_manager instead.
	--self.projectile_int = 1.5
	self.arrow_time_spent = 0

	--self.explode_int = 2.5
	self.barrel_time_spent = 0
	
	self.enemies_in_range = {}

	--self.hp = 100
	--self.max_hp = 100
end

local function get_closest_enemy(self)
	local closest = nil
	local min_dist = 999999
	
	for key, enemyu_coll in pairs(self.enemies_in_range) do
		if go.exists(enemyu_coll) then
			local dist = vmath.length_sqr(go.get_position(enemyu_coll) - go.get_position())
			if dist < min_dist then
				min_dist = dist
				closest = enemyu_coll
			end
		else
			-- clear key out of table
			self.enemies_in_range[key] = nil
		end
	end
	return closest
end

local function spawn_projectile(self)
	local closest = get_closest_enemy(self)
	local dir = vmath.normalize(go.get_position(closest) - go.get_position())
	
	factory.create("/factory_parent#projectile_factory", go.get_position(), nil,
	{
		direction = dir
	}
	, 1)
end

local function spawn_explode(self)
	local closest = get_closest_enemy(self)
	--local dir = vmath.normalize(go.get_position(closest) - go.get_position())

	factory.create("/factory_parent#explode_factory", go.get_position(), nil,
	{
		goal_point = go.get_position(closest)
	}
	, 1)
end

function update(self, dt)
	
	self.arrow_time_spent = self.arrow_time_spent + dt

	if self.arrow_time_spent >= game_manager.player_stats.arrowup_interval and #self.enemies_in_range > 0 then
		spawn_projectile(self)
		self.arrow_time_spent = 0
	end
	
	self.barrel_time_spent = self.barrel_time_spent + dt

	if self.barrel_time_spent >= game_manager.player_stats.barrelup_interval and #self.enemies_in_range > 0 then
		spawn_explode(self)
		self.barrel_time_spent = 0
	end
	
end

local function change_hp(self, amount)
	game_manager.player_stats.hp = game_manager.player_stats.hp + amount
	msg.post("/gui#left_panel", "update_stats")
	--print("current castle hp: ", game_manager.player_stats.hp)
end

function on_message(self, message_id, message, sender)
	if message.enter==true then
		-- add item using id as key and value
		self.enemies_in_range[message.other_id] = message.other_id 
	else
		--no point testing. let lua worry about it. it would searching once for the if anyway and then again to set it to nil
		self.enemies_in_range[message.other_id] = nil
	end
	if message_id == hash("trigger_response") then
		if message.other_group == hash("enemyu_coll") then
			table.insert(self.enemies_in_range, message.other_id)
		end
	end
	if message_id == hash("change_hp") and message.change_amount then
		change_hp(self, message.change_amount)
	end
end

As explained previously. the get_closest_enemy function call in spawn_projectile is likely returning nil.

The position evaluation will give a vector3(0,0,0) causing normalise function to return vector3(nan,nan.nan).

the other issue could be that the closest and current object are very close together - creating the same issue.

so the function should read

local function spawn_projectile(self)
	local closest = get_closest_enemy(self)

    if (closest==nil) then
       -- no projectile spawned.
       return false
    end

    -- get pos and position delta
    local pos = go.get_position()
    local delta = go.get_position(closest) - pos

    -- set up dir. 
    local dir = vmath.vector3(0,0,0)

    -- normalise if length isn't too small
    if (vmath.length_sqr(delta)>0.01) then 
        dir = vmath.normalize(delta)
	end

	factory.create("/factory_parent#projectile_factory", pos, nil,
	{
		direction = dir
	}
	, 1)
    -- projectile spawned
    return true
end

then in update loop

if self.arrow_time_spent >= game_manager.player_stats.arrowup_interval and #self.enemies_in_range > 0 then
        if spawn_projectile(self)==true then
            -- reset timer as projectile fired
		    self.arrow_time_spent = 0
        end
	end
  • Well so far the error did not pop out anymore for both of versions, with and without Andrew_Fowlers changes. I really don’t know at this point, I just continue with the tutorial, going back from time to time to address the error with the code I copied here. It goes into late game without any.

  • But when I upgrade my skills early(right from the start of the game with start of 1000 gold, testing purposes) for some reason (increase projectile damage and interval along with explode damage and interval) It would give a error only to the explosion script not to the projectile, so the projectile is fixed.

  • I noticed that when damage of projectile would one hit delete the enemy it will show the error so same as the projectile get_closest_enemy factory vmath.vector3(nan, nan, nan) except its the remove_value_from_table on the bomb script acting out(I think).

  • When I added in Andrew_Fowler changes to the castle_player.script it would throw this error

ERROR:SCRIPT: scripts/explode.script:29: argument #2 contains one or more values which are not numbers: vmath.vector3(nan, nan, nan)
stack traceback:
  [C]:-1: in function __mul
  scripts/explode.script:29: in function <scripts/explode.script:27>

The original gave the same error along with the bomb script/explode.script:29 error
So I guess it’s fixed, just don’t upgrade or I’ll just lock upgrade from the start, thanks for the help Andrew_Fowler.


Game File:
Second Defold Project
Asatte Incremental Game.zip (4.5 MB)

I downloaded and ran the project, but it didn’t produce any of the errors you mentioned. I tested it on both Windows and HTML5, and it worked fine.

It’s a very nice little project. i had fun playing it.

The enemies_in_range table keeps growing as the game progresses, which means the closest_enemy function will take longer to process over time. Based on my previous suggestions, I updated the castle code to only maintain current objects, keeping the table at a manageable size. I also made a few tweaks to improve the loop’s performance, including caching commonly used hashed variables so the engine doesn’t need to hash them repeatedly in each loop - which is good practice

I applied similar changes to the explode.script as well.

I also tweaked the closest_enemy function so that if an enemy has been fired upon recently it targets the next closest. It provides a more variation of projectile spawn - especially when the spawn frequency increases

In the projectile.script it is possible to eliminate the update function completely and move this onto the engine. So…

function update(self, dt)
	local pos = go.get_position()
	local new_pos = pos + self.speed * self.direction * dt
	go.set_position(new_pos)

	self.time_alive = self.time_alive + dt
	if self.time_alive > 4 then go.delete() end
end

can be replaced with these declarations at the top of the script

local LIFE_SPAN = 4
local POSITION = hash("position")

and these lines of code in the init function

-- calc end position
local pos_end = go.get_position() + self.speed * self.direction * LIFE_SPAN
go.animate(".", POSITION, go.PLAYBACK_ONCE_FORWARD, pos_end, go.EASING_LINEAR, LIFE_SPAN, 0, function() go.delete() end)

This is a great optimization, as it runs natively on the engine rather than relying on multiple Lua calls each loop. A similar approach could be applied to the explode script as well.

These performance improvements may seem small at first, but as the number of on-screen objects grows, the difference will become more noticeable.

Hopefully, you will find this useful.

tower defense.zip (4.5 MB)

Glad you had fun on this small project, it was a roller coaster.

Thanks for the added changes, I will dissect it and incorporate it to the code. I agree when there are multiple enemies on the late game, small improvements like this go a really long way. Can’t wait to finish this project/tutorial with the build .exe.