Assets
Modding
Modding
  • Introduction
  • How to add new items
  • How to add new events
  • How to add new stashes
  • How to add new tutorial message
  • How to add weapon HUD image
  • How to add new weapon and ammo
  • How to edit traders
  • How to add language
  • How to add new quest
  • How to add PDA markers
  • How to change in-game SFX
  • How to add ambient sounds to level
  • How to add new dialogue
  • How to change player data
  • How to add new enemy
Powered by GitBook
On this page

How to add new enemy

Tutorial will help you to undestand how to create new enemy profile and add it to the level

Files & folders needed:

assets/creatures/enemy.json

assets/levels/

assets/scripts/p_game.gd

assets/textures/

This is basic JSON structure of the enemy profile:

enemy.json
"soldier": {
	"weapons": ["w_ak","w_groza"],
	"attack_sound": "rifle_sound.wav",
	"miss_sounds": ["whine_1","whine_2","whine_3","whine_4","whine_5"],
	"texture": ["psoldier"],
	"accuracy": 0.85,
	"damage": [5,8],
	"health": [20,35],
	"exp": [5,8],
	"attack_delay": [2,5],
	"money": [35,65],
	"loot": [
		{
			"item": "medkit",
			"chance": 0.15,
			"amount": 1
		},
		{
			"item": "antirad",
			"chance": 0.03,
			"amount": 1
		},
		{
			"item": "medkit_army",
			"chance": 0.13,
			"amount": 1
		},
		{
			"item": "ammo_ak",
			"chance": 0.18,
			"amount": 1
		}
	]
}
soldier = Unique ID that used for spawning enemy at the level
weapons = one or multiple models names from assets/models/ for weapons
attack_sound = sound name when enemy shoot, file from assets/sounds/
miss_sounds = list of random sounds from assets/sounds/ when enemy miss shot
texture = list of random textures from assets/textures/
accuracy = percentage of chance to hit player, where 0.0 is 0%, 1.0 is 100% chance
damage = random range of damage to player between A and B value
health = random range of enemy health between A and B value
exp = random range of exp points as reward for player between A and B value
attack_delay = random range of enemy attack delay between A and B value
money = random range of money as reward for player between A and B value
loot = list of reward items after enemy death, where:
{
    item = item ID from assets/gameplay/items.json
    chance = chance of giving item to player between 0.0 and 1.0 (0% and 100%)
    amount = amount of items that will be given to player
}

All random values apply to the enemy when it's spawned at the level!

For example, let's use scientist texture from assets/textures/ for new enemy.

Create new profile at the end of the file.

enemy.json
"scientist_enemy": {
	"weapons": ["w_lr300"],
	"texture": ["pscientist"],
	"attack_sound": "rifle_sound.wav",
	"miss_sounds": ["whine_1","whine_2","whine_3","whine_4","whine_5"],
	"damage": [2.5,4.2],
	"health": [8,12],
	"accuracy": 0.75,
	"exp": [8,12],
	"attack_delay": [3,4],
	"money": [32,250],
	"loot": [
		{
			"item": "medkit",
			"chance": 0.75,
			"amount": 1
		},
		{
			"item": "antirad",
			"chance": 0.65,
			"amount": 1
		},
		{
			"item": "ammo_lr",
			"chance": 0.25,
			"amount": 1	
		}
	]
  }

So we have scientist enemy with some medkits and antirad items drop chance and ammo for LR rifle.

Now wee need somehow to test this profile, but how? There are two options:

  1. Spawn enemy with scripts, but need to be spawned an a eyezone for enemy too.

But let's looks like combat system works on that diagram:

When player spawn at the level, and you want to start combat, you need to place: , , .

Enemy will be attack if player is inside eyezone, if you left that eyezone with debug fly mode, enemies will stop attacking you. Also player can't interact with NPCs, Doors and Loot items while in combat mode (eyezone area).

Every enemy on the level must have their eyezone id where they will be attack player if he will be inside.

If two or more enemies has same eyezone id, then combat will be randomly selected what enemy will be attack and them wait attack delay from enemy.json file before start next attack. So attack queue is randomized.

Covers must cover enemies when they hide there. Every enemy must be setup with list of shoot animations based on their positions and cover size, for example on diagram, left AI can shoot with standing animation, sitting from left and right side of the cover.

This is perspective from old level editor:

To properly setup enemy on the level you need to understand what keys need to be edited at enemy object.

Enemy object keys
{
	"profile": "bandit",
	"zone_id": "forest_road_battle",
	"animations": ["shoot_stand_right", "shoot_sit_right", "shoot_sit_up_right"],
	"start_animation": "shoot_stand_start"
}
profile = Unique ID from assets/creatures/enemy.json
zone_id = eyezone unique ID
animations = attack animations based on cover, where enemy is
start_animation = start animation when AI spawned, you can make them sit or stand

Easy way to test new enemy is spawn eyezone and enemy at the start of the game at intro level.

Go to assets/scripts/p_game.gd file and look for:

p_game.gd
func on_level_changed(level_id):

There find "intro" level id and type this code to spawn eyezone and enemy:

p_game.gd -> on_level_changed(level_id):
"intro":
    # create eyezone with ID "my_new_combatzone1" at player position with size 2 by 2 by 2
    GM.create_eyezone("my_new_combatzone1",player.position,Vector3.ZERO,Vector3(2,2,2))
    # create enemy with profile ID "scientist_enemy" and set eyezone ID to "my_new_combatzone1" at XYZ 0,0,0 coordinates with 0,0,0 rotation
    # with start animation "shoot_stand_idle" and attack animations list ["shoot_stand_idle"] as we don't have covers
    GM.create_enemy("scientist_enemy","my_new_combatzone1",Vector3(0,0,0),Vector3(0,0,0),"shoot_stand_idle",["shoot_stand_idle"])

Check the new game:

We can fix rotation problem with calculating direction vector to player position with this code:

p_game.gd -> on_level_changed(level_id):
"intro":
    var enemy_pos_2d : Vector2
    var player_pos_2d : Vector2
    var direction
    # create eyezone with ID "my_new_combatzone1" at player position with size 2 by 2 by 2
    GM.create_eyezone("my_new_combatzone1",player.position,Vector3.ZERO,Vector3(2,2,2))
    # create enemy with profile ID "scientist_enemy" and set eyezone ID to "my_new_combatzone1" at XYZ 0,0,0 coordinates with 0,0,0 rotation
    # with start animation "shoot_stand_idle" and attack animations list ["shoot_stand_idle"] as we don't have covers
    var enemy = GM.create_enemy("scientist_enemy","my_new_combatzone1",Vector3(0,0,0),Vector3(0,0,0),"shoot_stand_idle",["shoot_stand_idle"])
    # using 2D vector with X and Y because it's easier to work with
    enemy_pos_2d = Vector2(enemy.global_position.x,enemy.global_position.z)
    player_pos_2d = Vector2(player.global_position.x, player.global_position.z)
    direction = -(enemy_pos_2d - player_pos_2d)
    # then rotate enemy towards player direction with atan2(x,y), last 1 is 100% of rotation
    # because lerp_angle() using in process function with delta for smooth rotation, but we
    # need rotate at one moment at the start, so we put last parameter as 1
    enemy.rotation.y = lerp_angle(enemy.rotation.y, atan2(direction.x, direction.y), 1)

But this type of "fix" is a little bit odd of course, you can set rotation as you want just in spawn parameters, because some covers need different rotations and it can be not always rotated to the player, like roof top enemies etc.

PreviousHow to change player data

Last updated 5 months ago

Player spawn in eyezone, that will trigger enemies linked to this eyezone id to start attack, using their animations
New enemy attack us with some delay, but he look at 0,0,0 rotation, you can change it of course!
Fixed rotation