How to create random 2D maps?

Good morning, I see on the forum, some ways to create collections of weapons, but I’m having trouble understanding something. How can I create a list of objects with about 100 weapons and call each one when the character changes on some bench on the map, with each weapon spawning randomly with the same chance of appearing?

This is the script for the laser gun, do I have to create 1 GO for each weapon or to change sprites and properties by creating an object with all weapons?

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

function init(self)
	msg.post('.', 'acquire_input_focus')
	self.speed = 500 								-- Velocidade do laser
	self.life = 5 / 10								-- Tempo de vida do laser em segundos
	sound.play('#laser1')
end

function update(self, dt)
	local pos = go.get_position()                   -- Cria a variavel 'pos' e atribui a posição atual do laser
    pos = pos + self.dir * self.speed * dt          -- A variável 'pos' recebe a soma dos eixos (x, y, z) vezes a velocidade vezes o deltaT('deltaT ajusta o fps do aparelho mobile para melhor eficiência')
    go.set_position(pos)

	self.life = self.life - dt
	if self.life < 0 then							-- Se o tempo do laser for menor que 0, inicia a contagem ate tempo de vida do laser após disparo
		self.life = 1000
		go.set_rotation(vmath.quat())
		self.speed = 0
		msg.post("#laser", "play_animation", {id = hash("acerta_alvo")})	-- Quando laser atingir o tempo de vida sem nenhuma colisão, ele chama a animação de explosão
	end

	if not self.collided then												-- Se objeto laser não colidir execute o código
		local new_position = pos + self.dir * self.speed * dt
		go.set_position(new_position)										-- Atualiza posição do objeto laser gerado

	else
		sprite.play_flipbook('#laser', hash('acerta_alvo'), go.delete())	-- Se colidir, delete o objeto laser
		go.delete(self.target)												-- Delete o objeto alvo do id correspondente
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash('contact_point_response') then					-- Se local de contato de 2 objetos
		self.collided = true												-- Colisão verdadeira
		self.target = message.other_id										-- Armazena id do objeto alvo

	elseif message_id == hash("animation_done") then            -- Se animação de explosão for chamada sem nenhuma colisão executa o seguinte código
		go.delete()                                         	-- Objeto laser deletado
	end
end

I’m calling the laser in the player’s script like this

	if self.shoot then
		local angle = math.atan2(self.dir.y, self.dir.x)
        local rot = vmath.quat_rotation_z(angle)
        local props = { dir = self.dir}
		factory.create( '#laser_factory', nil, rot, props)
		self.shoot = false
	end

I don’t know if it’s correct, but I’m trying to do the same to call the script that will contain all 100 weapons

	if self.action_buttom then
		print(self.action_buttom, '   buttom')
		factory.create(url, [position], [rotation], [properties], [scale])
		self.action_buttom = false
	end

How should the weapons work? Should they all look different? Should the bullets they fire be different? Should they fire bullets in different patterns or always one bullet at a time and in a straight line?

In general though, if you want to define many different weapons with different kinds of ammo, magazine size, reload time, bullets etc I’d probably define the weapons using Lua tables. When the player fires you can look up the required information in the Lua table to decide how fast the weapon should fire, what kind of bullets etc

Perhaps something like this to define weapons:

And here’s an example of a dynamic bullet script:

2 Likes

They all have sprites, fire rate, angle and different animations. My question is whether to create a list of objects as in your example, without creating a GO and script for each weapon.

If you plan to have 100 different weapons then I’d definitely recommend a data driven approach with the weapons defined in a data structure (Lua table or similar) and a weapon script that can process the data for use in-game.

1 Like

I will research about it, I really have no idea what it is. But thank you so much for everything

Like this:

-- this is our data structure for weapons
-- each weapon is represented by an entry in the weapons table
-- each weapon is indexed by the hash of the weapon type
local weapons = {
	[hash("revolver")] = {
		magazine = 6,
		rate_of_fire = 1,
		damage = 5,
		range = 50
	},
	[hash("uzi")] = {
		magazine = 40,
		rate_of_fire = 5,
		damage = 3,
		range = 200
	}
}

You then have a single weapon.script used for all weapons. The weapon script keeps track of which weapon that is used and looks up the needed information in the table above:

-- used to look up the weapon stats in the table above
go.property("type", hash("uzi"))


function init(self)
	self.weapon = weapons[self.type]
	self.magazine = weapon.magazine		-- current number of bullets in the magaize
	self.last_bullet_fired = 0			-- the last time a bullet was fired
end

local function fire_bullet(self)
	-- check that we have bullets in the magazine
	if self.magazine == 0 then
		print("No bullets! Reload!")
		return
	end
	
	-- check that we aren't firing too fast
	local now = socket.gettime()
	local time_since_last_bullet = now - self.last_bullet_fired
	if time_since_last_bullet < self.weapon.rate_of_fire then
		print("You can't shoot that fast")
		return
	end
	
	-- track when the bullet was fired
	self.last_bullet_fired = now
	-- decrease bullets in magazine
	self.magazine = self.magazine - 1
	
	-- create a bullet with properties matching the weapon
	local bullet_properties = {
		damage = self.weapon.damage,
		range = self.weapon.range
	}
	factory.create("#bulletfactory", nil, nil, bullet_properties)
	print("Bang!")
end

function on_input(self, action_id, action)
	-- try to shoot a bullet
	if action_id == hash("fire") and action.pressed then
		fire_bullet(self)
	-- reload weapon
	elseif action_id == hash("reload") and action.pressed then
		self.magazine = self.weapon.magazine
	end
end

What this means is that your code and your game is driven by the data in the weapons table above. You can load the data from an external source, say a json file or from the server, and this data is driving the behaviour in your game. You can easily modify the data and get the required behaviour without having to change any code. Perfect for rapid development and when you want a game that is easily moddable.

6 Likes

Good night, I’m trying to replicate the fire system randomly for weapons other than @britzl . But if I don’t give a value for the gun’s angle it returns null and if I pass the initial angle it doesn’t update. I apologize for being something so simple but I can’t solve it

This is the player.script

-- local FIRE = hash("shoot")
local BULLET = hash("bullet")
local BULLET_OFFSET = vmath.vector3(0, 1, 0)

local function spawn_bullet(self, angle)
	print('angulo aqui --- ', angle)
	local rot = angle

	local bullet_properties = {
		dir = self.dir,
		rate_of_fire = self.weapon.rate_of_fire,
		damage = self.weapon.damage,
		range = self.weapon.range,
		speed = self.weapon.speed,
	}

	factory.create("#armas_fabrica", go.get_world_position() + vmath.rotate(rot, BULLET_OFFSET), rot, bullet_properties)
	pprint('armas ----- ', bullet_properties)
end

local weapons = {
	{	name = "12",
		type = BULLET,
		rate_of_fire = 1,
		damage = 5,
		range = 50,
		speed = 200,
		fire_bullet = function (angle)
			for i=1,10 do
				spawn_bullet(angle * vmath.quat_rotation_z(math.rad(math.random(-10, 10))))
			end
		end
	},
	{	name = "uzi",
		type = BULLET,
		rate_of_fire = 5,
		damage = 3,
		range = 200,
		speed = 200,
		fire_bullet = function (angle)
			spawn_bullet(angle)
		end
	}
}

function init(self)
	msg.post('.', 'acquire_input_focus')
	msg.post("camera", "follow") -- <1>
	self.follow = true -- <2>
	self.direction = vmath.vector3() 			-- Função 'vmath.vector3()' retorna {x = 0, y = 0, z = 0}
	self.dir = vmath.vector3(0, 1, 0)
	self.speed = 500
	self.shoot = false
	self.action_buttom = false
	self.shoot_count = 0
	self.correction = vmath.vector3()			-- Variável para corrigir colisão de objetos
	self.hero_half_height = go.get('#heroi', "size.y")/1,5
	self.hero_half_width = go.get('#heroi', "size.x")/1,5
	self.screen_right = 5350 - self.hero_half_width
	self.screen_left = 0 + self.hero_half_width
	self.screen_top = 2950 - self.hero_half_height
	-- IMPORTANTE a sprite 'heroi' ultilizada, possui 12pixels a mais na parte superior,
	-- por isso a funcao bottom recebe 10 pixels adicionais para compensar o limite da tela
	self.screen_bottom = 10 + self.hero_half_height
	self.weapon = weapons[1]
	self.velocity = vmath.vector3()
	self.weapon_angle = vmath.quat()
end

function update(self, dt)
	self.correction = vmath.vector3()						-- Atualiza a correção de colisão de objetos

	if self.shoot then
		spawn_bullet(self, self.weapon_angle)

		self.shoot = false
	end

	if self.direction ~= vmath.vector3() then										-- Se houve alguma movimentacao do jogador
		local current_position = go.get_position() 									-- Pega a posicao atual do jogador
		local new_position = current_position + self.direction * self.speed * dt 	-- Atualiza a nova posicao do jogador

		if self.direction.x < 0 then												-- Se movimentar para a esquerda da tela
			if new_position.x < self.screen_left then								-- Se o jogador esta no limite esquerdo da tela
				new_position.x = self.screen_left 									-- Limite do lado esquerdo da tela
			end

			sprite.set_hflip("#heroi", true)										-- Atualiza a face do heroi para a direita

		elseif self.direction.x > 0 then											-- SE NAO, movimentar para a direita da tela
			if new_position.x > self.screen_right then								-- Se o jogador esta no limite direito da tela
				new_position.x = self.screen_right 									-- Limite do lado direito da tela
			end

			sprite.set_hflip("#heroi", false)										-- Atualiza a face do heroi para a esquerda
		end


		if new_position.y > self.screen_top then									-- Se o jogador esta no limite de cima da tela
			new_position.y = self.screen_top 										-- Limite do lado de cima da tela
		end

		if new_position.y < self.screen_bottom then									-- Se o jogador esta no limite de baixo da tela
			new_position.y = self.screen_bottom 									-- Limite do lado de baixo da tela
		end

		go.set_position(new_position)												-- Muda o jogador para a nova posicao no jogo
		self.direction = vmath.vector3()											-- Corrige a direcao do ultimo movimento
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then									-- Se mensagem de contato for emitida, faça
		if message.distance > 0 then
		  local proj = vmath.project(self.correction, message.normal * message.distance)	-- Primeiro, projete a correção acumulada no vetor de penetração

		  if proj < 1 then
			local comp = (message.distance - message.distance * proj) * message.normal		--  Cuidado apenas com as projeções que não excedem.

			go.set_position(go.get_position() + comp)										-- Aplique a compensação
			self.correction = self.correction + comp										-- Correção acumulada completa
		  end
		end
	end
end

local function change_weapon(self, index)
	self.weapon = weapons[index]
	self.weapon_angle = vmath.quat()
	self.velocity = vmath.vector3()
	self.correction = vmath.vector3()
	pprint('mudar arma ----- ', self.weapon)
end

function on_input(self, action_id, action)
	if action_id == hash('move_up') then  -- Se movimento para cima
		self.direction.y = 1
	elseif action_id == hash('move_down') then 	-- Se movimento para baixo
		self.direction.y = -1
	end

	if action_id == hash('move_left') then  -- Se movimento para esquerda
		self.direction.x = -1
	elseif action_id == hash('move_right') then  -- Se movimento para direita
		self.direction.x = 1
	end

	if action_id == hash("action") and action.pressed then
		change_weapon(self, math.random(1, #weapons))
	end

	if action_id == hash("shoot") and action.repeated then
		self.shoot = true
	end

    if vmath.length(self.direction) > 0 then
        self.dir = vmath.normalize(self.direction)
    end
end

--[[         			 DOCUMENTAÇÃO
	<2> Envie uma mensagem para o objeto do jogo da câmera dizendo-lhe para seguir este objeto do jogo.
	<3> Acompanhe se a câmera está seguindo este objeto de jogo ou não.
	<4> Alterne entre seguir e não seguir o objeto do jogo quando o botão esquerdo do mouse é clicado ou a tela é tocada.
--]]

I’m trying to pass the function to change the angle like this

function update(self, dt)
	self.correction = vmath.vector3()						-- Atualiza a correção de colisão de objetos

	if self.shoot then
		spawn_bullet(self, self.weapon.fire_bullet())

		self.shoot = false
	end

Ok, so this function takes two arguments: self and angle. In the same script you also call:

This looks good, although I don’t see self.weapon_angle changing?

Now, if we look at the other snippet of code:

This looks strange. Here you call the self.weapon.fire_bullet() function as the second argument. And the result of that function call gets passed as the second argument to spawn_bullet().

But this function doesn’t return any value and instead also call the spawn_bullet function! What I believe you want to do is this:

	if self.shoot then
		self.weapon.fire_bullet()

		self.shoot = false
	end
1 Like

Good afternoon, I’m trying to create a weapon with several shots in different directions. But what is happening is that everyone is only changing the angle and not the direction. I appreciate if someone can help me.

This is player.script

-- local FIRE = hash("shoot")
local BULLET = hash("bullet")
local BULLET_OFFSET = vmath.vector3(0, 1, 0)

local function spawn_bullet(angle, properties)
	print('angulo aqui --- ', angle)
	pprint('armas ----- ', properties)
	factory.create("#armas_fabrica", go.get_position() + vmath.rotate(angle, BULLET_OFFSET), angle, properties)
end

local weapons = {
	{	name = "12",
		type = BULLET,
		rate_of_fire = 1,
		damage = 5,
		range = 50,
		speed = 200,
		fire_bullet = function (self, angle)
			for i=1,10 do
				spawn_bullet(angle * vmath.quat_rotation_z(math.rad(math.random(-20, 20))), {dir = self.dir, speed = 200, type = BULLET, damage = 2, rate_of_fire = self.weapon.rate_of_fire })
			end
		end
	},
	{	name = "uzi",
		type = BULLET,
		rate_of_fire = 5,
		damage = 3,
		range = 200,
		speed = 200,
		fire_bullet = function (self, angle)
			spawn_bullet(angle, {dir = self.dir, speed = 200, type = BULLET, damage = 1, rate_of_fire = self.weapon.rate_of_fire})
		end
	}
}

function init(self)
	msg.post('.', 'acquire_input_focus')
	msg.post("camera", "follow") -- <1>
	self.follow = true -- <2>
	self.direction = vmath.vector3() 			-- Função 'vmath.vector3()' retorna {x = 0, y = 0, z = 0}
	self.dir = vmath.vector3(0, 1, 0)
	self.speed = 500
	self.shoot = false
	self.action_buttom = false
	self.shoot_count = 0
	self.correction = vmath.vector3()			-- Variável para corrigir colisão de objetos
	self.hero_half_height = go.get('#heroi', "size.y")/1,5
	self.hero_half_width = go.get('#heroi', "size.x")/1,5
	self.screen_right = 5350 - self.hero_half_width
	self.screen_left = 0 + self.hero_half_width
	self.screen_top = 2950 - self.hero_half_height
	-- IMPORTANTE a sprite 'heroi' ultilizada, possui 12pixels a mais na parte superior,
	-- por isso a funcao bottom recebe 10 pixels adicionais para compensar o limite da tela
	self.screen_bottom = 10 + self.hero_half_height
	self.weapon = weapons[1]
	self.velocity = vmath.vector3()
	self.weapon_angle = vmath.quat()
end

function update(self, dt)
	self.correction = vmath.vector3()						-- Atualiza a correção de colisão de objetos

	if self.shoot then
		local now = socket.gettime()
		self.weapon.fire_bullet(self, self.weapon_angle)

		self.shoot = false
	end

	if self.direction ~= vmath.vector3() then										-- Se houve alguma movimentacao do jogador
		local current_position = go.get_position() 									-- Pega a posicao atual do jogador
		local new_position = current_position + self.direction * self.speed * dt 	-- Atualiza a nova posicao do jogador

		if self.direction.x < 0 then												-- Se movimentar para a esquerda da tela
			if new_position.x < self.screen_left then								-- Se o jogador esta no limite esquerdo da tela
				new_position.x = self.screen_left 									-- Limite do lado esquerdo da tela
			end

			sprite.set_hflip("#heroi", true)										-- Atualiza a face do heroi para a direita

		elseif self.direction.x > 0 then											-- SE NAO, movimentar para a direita da tela
			if new_position.x > self.screen_right then								-- Se o jogador esta no limite direito da tela
				new_position.x = self.screen_right 									-- Limite do lado direito da tela
			end

			sprite.set_hflip("#heroi", false)										-- Atualiza a face do heroi para a esquerda
		end


		if new_position.y > self.screen_top then									-- Se o jogador esta no limite de cima da tela
			new_position.y = self.screen_top 										-- Limite do lado de cima da tela
		end

		if new_position.y < self.screen_bottom then									-- Se o jogador esta no limite de baixo da tela
			new_position.y = self.screen_bottom 									-- Limite do lado de baixo da tela
		end

		go.set_position(new_position)												-- Muda o jogador para a nova posicao no jogo
		self.direction = vmath.vector3()											-- Corrige a direcao do ultimo movimento
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then									-- Se mensagem de contato for emitida, faça
		if message.distance > 0 then
		  local proj = vmath.project(self.correction, message.normal * message.distance)	-- Primeiro, projete a correção acumulada no vetor de penetração

		  if proj < 1 then
			local comp = (message.distance - message.distance * proj) * message.normal		--  Cuidado apenas com as projeções que não excedem.

			go.set_position(go.get_position() + comp)										-- Aplique a compensação
			self.correction = self.correction + comp										-- Correção acumulada completa
		  end
		end
	end
end

local function change_weapon(self, index)
	self.weapon = weapons[index]
	self.weapon_angle = vmath.quat()
	self.velocity = vmath.vector3()
	self.correction = vmath.vector3()
	pprint('mudar arma ----- ', self.weapon)
end

function on_input(self, action_id, action)
	if action_id == hash('move_up') then  -- Se movimento para cima
		self.direction.y = 1
	elseif action_id == hash('move_down') then 	-- Se movimento para baixo
		self.direction.y = -1
	end

	if action_id == hash('move_left') then  -- Se movimento para esquerda
		self.direction.x = -1
	elseif action_id == hash('move_right') then  -- Se movimento para direita
		self.direction.x = 1
	end

	if action_id == hash("action") and action.pressed then
		change_weapon(self, math.random(1, #weapons))
	end

	if action_id == hash("shoot") and action.repeated then
		local pos = go.get_position()
		local angle = -math.atan2(pos.x, pos.y)
		local quat = vmath.quat_rotation_z(angle)
		self.weapon_angle = quat
		self.shoot = true
	end

    if vmath.length(self.direction) > 0 then
        self.dir = vmath.normalize(self.direction)
    end
end

--[[         			 DOCUMENTAÇÃO
	<2> Envie uma mensagem para o objeto do jogo da câmera dizendo-lhe para seguir este objeto do jogo.
	<3> Acompanhe se a câmera está seguindo este objeto de jogo ou não.
	<4> Alterne entre seguir e não seguir o objeto do jogo quando o botão esquerdo do mouse é clicado ou a tela é tocada.
--]]

Another question is that I’m trying to update the face of the shots but it’s not working with set_hflip

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

function init(self)
	msg.post('.', 'acquire_input_focus')
	self.speed = 400 								-- Velocidade do laser
	self.life = 1 / 2								-- Tempo de vida do laser em segundos
	sound.play('#laser1')
end

function update(self, dt)
	local pos = go.get_position()                   -- Cria a variavel 'pos' e atribui a posição atual do laser
	print(self.dir.x, 'esse eh o dirrr')
	if self.dir.x > 0 then
		sprite.set_hflip("#armas", true)
	else
		sprite.set_hflip("#armas", false)
	end

	
	pos = pos + self.dir * self.speed * dt          -- A variável 'pos' recebe a soma dos eixos (x, y, z) vezes a velocidade vezes o deltaT('deltaT ajusta o fps do aparelho mobile para melhor eficiência')
    go.set_position(pos)

	self.life = self.life - dt
	if self.life < 0 then							-- Se o tempo do laser for menor que 0, inicia a contagem ate tempo de vida do laser após disparo
		self.life = 1000
		go.set_rotation(vmath.quat())
		msg.post("#armas", "play_animation", {id = hash("acerta_alvo")})	-- Quando laser atingir o tempo de vida sem nenhuma colisão, ele chama a animação de explosão
	end

	if not self.collided then												-- Se objeto laser não colidir execute o código
		local new_position = pos + self.dir * self.speed * dt
		go.set_position(new_position)										-- Atualiza posição do objeto laser gerado

	else
		go.delete()															-- Se colidir, delete o objeto laser
		go.delete(self.target)												-- Delete o objeto alvo do id correspondente
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash('contact_point_response') then					-- Se local de contato de 2 objetos
		self.collided = true												-- Colisão verdadeira
		self.target = message.other_id										-- Armazena id do objeto alvo

	elseif message_id == hash("animation_done") then            -- Se animação de explosão for chamada sem nenhuma colisão executa o seguinte código
		go.delete()                                         	-- Objeto laser deletado
	end
end

Ok, so in your weapon script you have a dir property:

And you use it to move the bullet:

This looks good! So the problem must be that you are not setting dir to the correct value when you spawn the bullet.

You pass self.weapon_angle when you call the fire_bullet function on the weapon:

But you are not using this angle when you call spawn_bullet:

Instead you pass self.dir which I assume is the direction the player is travelling in. Shouldn’t you pass angle here instead?

1 Like