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.
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:
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.
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.