Is it possible to make a dictionary into a random player generator?

Godot Version

4.6

Question

I am looking to make a football game, and I’d like to make a random character generator. I’m still new to Godot, and I’m not much of a coder at all. I’m trying to learn.

I have been watching a bunch of YouTube videos and it’s taught me some things, but I’m of very average intelligence.

I’m trying to figure out if I could make a dictionary that doubles as a character generator by defining positions and skills, and then matching the positions list to a skill value. I’ll post an example of code that I’ve done, and you will laugh, but that’s ok. I just want to know:

A) is this even a possible idea?

B) where am I going wrong? I figure I’m wrong in several areas, but I don’t know enough about the syntax yet to understand where.

If you share code, please wrap it inside three backticks or replace the code in the next block:


extends Node2D

class player

func _ready():
     #this code is to separate the positions in the inspector
     @export var positions:Dictionary={
              "qb"=0,
              "hb"=1,
              "te"=2 
      }
      #this code is to add multiple players at the positions
func add_positions(position,depth):
      positions["qb"]=2
      positions["hb"]=3
      positions["te"]=2
      #this code is to make the skills
      @export var skills:Dictionary={
              "run"=0,
              "throw"=1,
              "block"=2
      }
     #this code is to assign skills to players
func add_skill(qb,skill)
 
      if positions.has(qb,depth):
         and if skills.has(throw)
         str(qb,depth) + str(throw) = qb add_skill```

now, I know that none of this is correct. 
I just want to know if such an idea is possible. Do I have to use a resource? Is it better to use enums in such a case? and etc. 

Thank you for your help. 

Hey, sorry not really an answer but I would think this is possible. Have you looked at random character generator for a rim world type game? I feel like it would be pretty comparable since you have a set of variables for stats and maybe some special talents or classes for what position they play and excel at

It might help if you explained what this looks like without the code.

I’m not sure of the syntax but yes it will be possible from what I know and a dictionary should work

Thank you for the replies, everyone.

@dragonforge-dev Yeah, sorry, my code was probably more confusing than helpful.

What I’m trying to build is this:

A player node containing 2 dictionaries.

1 dictionary is for positions, both offensive and defensive (qb, hb, te, de, dt, etc.), plus a position simply called Athlete.

1 dictionary for skills (speed, throwing, agility, etc.).

The idea is that the skills dictionary would assign random values at each skill.

Depending on what values the player receives at each skill, the game would assign him a position. For instance, if his “throw” value and “coverage read” values are above a certain threshold (say, 78 as a random example), the game would be likely to tag him as a QB. This would be changeable, but the game simply assigns it as a starting point based on his strengths.

I want all players to be editable, but I just wanted a player generator to create starting point skill ratings and position markers.

I haven’t looked that up, no. I’ll try to find something on that. Thank you.

Might be a worth while rabbit hole to go down :slight_smile: sounds like a fun project

What would be the values of the positions dictionary? To store the current position of a unit, it would only need one variable and an enum.

The skills could be grouped in either a dictionary or a Resource, depending on if you need custom methods for them or not.

Why not to make skills dictionary into an array/list, then in the _ready() method you generate a random index using randi_range(0,skills_array.size()) to index skills array/list, finally you use the indexed value to access a position in position dictionary.

The position values would be 2, I think. One would mark the position itself (i.e. qb=0, center=1, guard=2, tackle=3, etc.) and the second would mark the depth (i.e. qb 1 (starter), qb2 (backup), etc.)

The trick is, I wouldn’t want to have locked-in depth charts. Generally (I don’t know how much you follow football, so sorry if this is pedantic), football has a 53 man active roster and a 17 man practice squad (players who practice with the team but do not play in the games). I want to be able to have 2 qbs and 4 hbs, or 3 qbs and 3 hbs, or 0 qbs and 5 fullbacks. I want the roster to be completely up to the player, so they can design their strategy around it.

That’s why I need to decouple the “player” from the “position”, and let skills determine the starting point, but then let the player edit the position assignment at will.

Now that I type that out, I think I’m going about it all wrong. The player and the skills should be in one node and the position should probably be its own node, right?

But would that assign the same randi values to each player, every time?

Because you could do the same thing using the pick_random() function:

var skills: Array

func _ready() -> void:
	var skill = skills.pick_random()

While we are on the subject, you should never include the type in the name of a variable. It just makes it unnecessarily long. The exception being when it is the thing. Like if you had a Skill Resource, and you did something like:

@export var skill: Skill

Or it was an @onready variable and you did something like:

@onready var timer: Timer = $Timer

No. Every time you start your game, Godot automatically seeds the random number generator with the current time, so you are always guaranteed a different random number seed. Whether you’re using functions like randi(), randf_range(), or pick_random().

I think you want to consider Resource objects.

This latest post you’re actually describing what you want, instead of how you planned to implement it. So here’s how I would try implementing it based on your description. (Because while I know how the game is played, but not things about the roster.)

Player Resource

So first, based on your description, I made a quick Resource object named Player. (Icon is from SVG Repo: https://www.svgrepo.com/svg/387781/helmet)

@tool
@icon("uid://c4pnunpb84rdb")
class_name Player extends Resource

enum Position {
	ATHLETE,
	QUARTERBACK,
	CENTER,
	GUARD,
	TACKLE,
	BACK,
	WIDE_RECEIVER,
	TIGHT_END,
}

enum Depth {
	STARTER,
	BACKUP,
	THIRD_TIER,
}

@export var position: Position = Position.ATHLETE
@export var position_depth: Depth = Depth.STARTER
@export_range(1.0, 100.0, 1.0) var speed: float
@export_range(1.0, 100.0, 1.0) var throwing: float
@export_range(1.0, 100.0, 1.0) var agility: float


func _init() -> void:
	speed = randf_range(1.0, 100.0)
	throwing = randf_range(1.0, 100.0)
	agility = randf_range(1.0, 100.0)
	
	if throwing >= 50.0:
		position = Position.QUARTERBACK
	elif speed >= 50.0:
		position = Position.BACK
	elif agility >= 50.0:
		position = Position.TIGHT_END

Then create a node with this script, and add them in the Inspector:

extends Node

@export var roster: Array[Player]

Each of the players in the screen shot was randomly generated as soon as I added them to the editor. I did not edit them at all.

An excellent answer @dragonforge-dev

:exploding_head: Oh, wow. This is really cool. So, I need to use resources for this kind of work.

Thank you very much. Since you seem to be extremely good at this, could I ask:

  1. how do I add “and” statements in this kind of expression? For instance:

```if throwing >= 50.0 and coverage_read>=70
position = Position.QUARTERBACK
elif speed >= 50.0:
position = Position.BACK
elif agility >= 50.0:
position = Position.TIGHT_END```

I always get an error thrown back at me when I try to use and modifiers, so I know I’m doing them wrong.

  1. would it be advisable to code behavior of each position in its own node to keep them seperate, or would it be better to code them all in one resource?

  2. do you know of any resources/tutorial videos that you would recommend for what I’m doing? I’ve watch dev worm’s videos on dictionaries and resources, and I’ve watched Brackey’s video on learning GDScript in one hour. Clearly I’m not good at this. I need to learn more, especially about functions; there are a lot of function types I’m not aware of.

My main objective here is to code the fundamental player behavior and game parameters before everything else. Most tutorials start with a player and movement and platforms and that kind of thing. I’m not even sure whether I want to be controlling a player or not yet. I am thinking of making this a coach sim, and I want to make sure the game of football (formation creator, play creator, lineups, pre-snap (beginning of play) behavior, snap from center, post snap behavior (actual play resolution), penalties, injuries, player pain and fatigue, roster management, player acquisition (draft and free agency), player training and progression through practice) is coded before worrying about sprites and button inputs are even a thought.

Therefore, I need to understand a lot more about coding multiple npc behaviors and how to use Control nodes to govern player objects.

I have definitely bitten off more than I can chew. :sweat_smile:

Thank you again for your reply. It really helps.

You need to put the tick marks on the lines abov and below the code, and you’ll need to copy and paste it from Godot again to get the tabs back in there, but then I can help you with it. Although it looks like you are using and correctly.

Start with one, and if it gets too big, break it into smaller pieces.

Here’s a couple of posts on the subject that might help. This first one might actually help with your first question:

This one will lead you down a nice rabbit-hole of other links I’ve posted for people. I answer questions like this a lot on here. Let me know if you have more questions after reading.

The fact that you know what you want to build will take you far. The more videos you watch and tutorials you read, the better your vocabulary about programming and making games will be. In turn that will make your questions more targeted, and the answers will get better. Jump in, and have fun!

I keep getting a message saying that script inherits from type Resource so it can’t be assigned to an object of type Node.

Do you know what I’m doing wrong?

Also, that post about Item/Weapon/Slot connecting in one that I will be coming back to OFTEN. lol.

That’s amazing. I tried using AI to get an idea of Godot code, but it was terrible. Each response was an idea of how something might work, but with zero context. What you’ve outlined is actually sensible. THANK YOU AGAIN!

Yes, you need to use the Node code above and add the Array of your Resource.

extends Node

@export var roster: Array[Player]

Yup. LLMs will lead you astray. There are a TON of long posts on that here. Just search “LLM” or “AI” and you’ll find them. I go into great detail, as do others, on why they don’t work for learning.

I am not getting errors with this code, but I’m also not seeing any positions populate in the Inspector under the Roster heading.

It might be because I was trying to modify what you put up. Here’s what I currently have:

@tool
@icon("icon.svg")
class_name Player
extends Resource 
 
 

enum Position {
	ATHLETE,
	QUARTERBACK,
	CENTER,
	GUARD,
	TACKLE,
	HALFBACK,
	FULLBACK,
	WIDE_RECEIVER,
	TIGHT_END,
	DEFENSIVE_END,
	DEFENSIVE_TACKLE,
	OUTSIDE_LINEBACKER,
	INSIDE_LINEBACKER,
	STRONG_SAFETY,
	CORNERBACK,
	FREE_SAFETY,
	LONG_SNAPPER,
	KICKER,
	PUNTER
}

enum Depth {
	STARTER,
	BACKUP,
	THIRD_STRING,
	FOURTH_STRING,
	FIFTH_STRING,
	SIXTH_STRING,
	EIGHTH_STRING,
	NINTH_STRING,
	TENTH_STRING
}

@export var position: Position = Position.ATHLETE
@export var position_depth: Depth = Depth.STARTER
@export_range(1.0, 100.0, 1.0) var speed: float
@export_range(1.0, 100.0, 1.0) var strength: float
@export_range(1.0, 100.0, 1.0) var agility: float
@export_range(1.0, 100.0, 1.0) var throw_accuracy: float
@export_range(1.0, 100.0, 1.0) var throw_read: float
@export_range(1.0, 100.0, 1.0) var catching: float
@export_range(1.0, 100.0, 1.0) var routes: float
@export_range(1.0, 100.0, 1.0) var run_read: float
@export_range(1.0, 100.0, 1.0) var run_block: float
@export_range(1.0, 100.0, 1.0) var pass_block: float
@export_range(1.0, 100.0, 1.0) var pass_rush: float
@export_range(1.0, 100.0, 1.0) var run_gap: float
@export_range(1.0, 100.0, 1.0) var run_fit: float
@export_range(1.0, 100.0, 1.0) var blitz: float
@export_range(1.0, 100.0, 1.0) var man_cover: float
@export_range(1.0, 100.0, 1.0) var zone_cover: float
@export_range(1.0, 100.0, 1.0) var kick_accuracy: float
@export_range(1.0, 100.0, 1.0) var punt_accuracy: float
@export_range(1.0, 100.0, 1.0) var snap_accuracy: float

func _init() -> void:
	speed = randf_range(50.0, 100.0)
	strength = randf_range(50.0, 100.0)
	agility = randf_range(50.0, 100.0)
	throw_accuracy = randf_range(50.0, 100.0)
	throw_read = randf_range(50.0, 100.0)
	catching = randf_range(50.0,100.0)
	routes = randf_range(50.0,100.0)
	run_read = randf_range(50.0,100.0)
	run_block = randf_range(50.0,100.0)
	pass_block = randf_range(50.0,100.0)
	pass_rush = randf_range(50.0,100.0)
	run_gap = randf_range(50.0,100.0)
	run_fit = randf_range(50.0,100.0)
	blitz = randf_range(50.0,100.0)
	man_cover = randf_range(50.0,100.0)
	zone_cover = randf_range(50.0,100.0)
	kick_accuracy = randf_range(50.0,100.0)
	punt_accuracy = randf_range(50.0,100.0)
	snap_accuracy = randf_range(50.0,100.0)
	
	if speed >= 83.0 and strength >= 81.0 and agility >= 82.0 and throw_read <= 79.0 and run_read <= 81.0 and zone_cover <= 80.0 :
		position = Position.ATHLETE
	if throw_accuracy >= 80.0 and throw_read >= 80.0:
		position = Position.QUARTERBACK
	elif speed >= 85.0 and strength >= 75.0 and run_read >= 82.0 : 
		position = Position.HALFBACK
	elif speed >= 78.0 and speed <=83.0 and strength >= 80.0 and run_read >= 78.0 : 
		position = Position.FULLBACK 
	elif agility >= 50.0:
		position = Position.TIGHT_END
```

I wanted to keep the values between 50 and 100 and add extra parameters to keep player positions within tighter ranges.

That way, I won’t have players with 2 Speed. Football requires tight ranges, especially at the pro level, where speed is within hundredths of a second, often.

I also need to add height and weight attributes, which I have started on, as you might notice.

Do you think my parameters are too stringent for the program to find any players adequate for the position?

I did put that Node code into a Node script, by the way. That is in the game, and I named it Roster like you did, and it does show up in the Inspector. It just doesn’t have any players populating underneath it.

Edit: just realized my height and weight parameters are gone. Must have happened when I was trying to fix the error messages. So they aren’t there.

Further explanation: the depth enum was changed too, because I wanted the player to be able to put as many players of any type at the starting position on the roster. So, if you want to roster 11 QBs on offense and line them up at the O line positions and all the skill positions, go ahead! The results won’t be great, but… you can do it.

Ok, so I’ve got this code:

@tool
@icon("icon.svg")
class_name Player
extends Resource 
 
 

enum Position {
	ATHLETE,
	QUARTERBACK,
	CENTER,
	GUARD,
	TACKLE,
	HALFBACK,
	FULLBACK,
	WIDE_RECEIVER,
	TIGHT_END,
	DEFENSIVE_END,
	DEFENSIVE_TACKLE,
	OUTSIDE_LINEBACKER,
	INSIDE_LINEBACKER,
	STRONG_SAFETY,
	CORNERBACK,
	FREE_SAFETY,
	LONG_SNAPPER,
	KICKER,
	PUNTER
}

enum Depth {
	STARTER,
	BACKUP,
	THIRD_STRING,
	FOURTH_STRING,
	FIFTH_STRING,
	SIXTH_STRING,
	EIGHTH_STRING,
	NINTH_STRING,
	TENTH_STRING
}

@export var position: Position = Position.ATHLETE
@export var position_depth: Depth = Depth.STARTER
@export_range(50.0, 100.0, 1.0) var speed: float
@export_range(50.0, 100.0, 1.0) var strength: float
@export_range(50.0, 100.0, 1.0) var agility: float
@export_range(50.0, 100.0, 1.0) var throw_accuracy: float
@export_range(50.0, 100.0, 1.0) var throw_read: float
@export_range(50.0, 100.0, 1.0) var catching: float
@export_range(50.0, 100.0, 1.0) var routes: float
@export_range(50.0, 100.0, 1.0) var run_read: float
@export_range(50.0, 100.0, 1.0) var run_block: float
@export_range(50.0, 100.0, 1.0) var pass_block: float
@export_range(50.0, 100.0, 1.0) var pass_rush: float
@export_range(50.0, 100.0, 1.0) var run_gap: float
@export_range(50.0, 100.0, 1.0) var run_fit: float
@export_range(50.0, 100.0, 1.0) var blitz: float
@export_range(50.0, 100.0, 1.0) var man_cover: float
@export_range(50.0, 100.0, 1.0) var zone_cover: float
@export_range(50.0, 100.0, 1.0) var kick_accuracy: float
@export_range(50.0, 100.0, 1.0) var punt_accuracy: float
@export_range(50.0, 100.0, 1.0) var snap_accuracy: float

func _init() -> void:
	speed = randf_range(50.0, 100.0)
	strength = randf_range(50.0, 100.0)
	agility = randf_range(50.0, 100.0)
	throw_accuracy = randf_range(50.0, 100.0)
	throw_read = randf_range(50.0, 100.0)
	catching = randf_range(50.0,100.0)
	routes = randf_range(50.0,100.0)
	run_read = randf_range(50.0,100.0)
	run_block = randf_range(50.0,100.0)
	pass_block = randf_range(50.0,100.0)
	pass_rush = randf_range(50.0,100.0)
	run_gap = randf_range(50.0,100.0)
	run_fit = randf_range(50.0,100.0)
	blitz = randf_range(50.0,100.0)
	man_cover = randf_range(50.0,100.0)
	zone_cover = randf_range(50.0,100.0)
	kick_accuracy = randf_range(50.0,100.0)
	punt_accuracy = randf_range(50.0,100.0)
	snap_accuracy = randf_range(50.0,100.0)
	
	if speed >= 83.0 and strength >= 81.0 and agility >= 82.0 and throw_read <= 79.0 and run_read <= 81.0 and zone_cover <= 80.0 :
		position = Position.ATHLETE
	elif throw_accuracy >= 80.0 and throw_read >= 80.0:
		position = Position.QUARTERBACK
	elif speed >= 85.0 and strength >= 75.0 and run_read >= 82.0 : 
		position = Position.HALFBACK
	elif speed >= 78.0 and speed <=83.0 and strength >= 80.0 and run_read >= 78.0 : 
		position = Position.FULLBACK 
	elif speed >= 81.0 and agility >= 77.0 and catching >= 82.0 and run_block >= 79.0 and pass_block >= 75.0:  
		position = Position.TIGHT_END
	elif speed >= 86.0 and agility >= 82.0 and catching >= 84.0 and routes >= 78.0 :  
		position = Position.WIDE_RECEIVER
	elif speed <= 76.0 and strength >= 85.0 and run_block >= 75.0 and pass_block >= 75.0 and snap_accuracy >= 83.0 :
		position = Position.CENTER 
	elif speed <= 75.0 and strength >= 85.0 and run_block >= 72.0 and pass_block >= 78.0 :
		position = Position.TACKLE
	elif speed <= 76.0 and strength >= 85.0 and run_block >= 78.0 and pass_block >= 72.0 :
		position = Position.GUARD 
	elif speed >= 75.0 and strength >= 83.0 and run_gap >= 72.0 and pass_rush >= 78.0 :
		position = Position.DEFENSIVE_END
	elif speed <= 74.0 and strength >= 85.0 and run_gap >= 78.0 and pass_rush >= 72.0 :
		position = Position.DEFENSIVE_TACKLE
	elif speed >= 77.0 and strength >= 78.0 and run_fit >= 74.0 and blitz >= 73.0 and man_cover >= 73.0 and zone_cover >= 68.0 :
		position = Position.INSIDE_LINEBACKER
	elif speed >= 77.0 and strength >= 78.0 and agility >= 78.0 and run_fit <= 73.0 and blitz >= 74.0 and man_cover <= 74.0 and zone_cover <= 68.0 :
		position = Position.OUTSIDE_LINEBACKER
	elif speed >= 83.0 and strength >= 76.0 and blitz >= 72.0 and run_fit >= 70.0 and man_cover >= 75.0 and zone_cover >=78.0 :
		position = Position.STRONG_SAFETY
	elif speed >= 85.0 and strength >= 73.0 and man_cover >= 76.0 and zone_cover >= 77.0 :
		position = Position.FREE_SAFETY
	elif speed >= 86.0 and agility >= 82.0 and man_cover >= 80.0 and zone_cover >= 74.0 :
		position = Position.CORNERBACK
	elif speed < 75.0 and strength >= 80.0 and kick_accuracy >= 84.0 :
		position = Position.KICKER
	elif speed < 75.0 and strength >= 80.0 and kick_accuracy <= 84.0 and punt_accuracy >= 84.0 :
		position = Position.KICKER
	elif speed < 75.0 and strength >= 80.0 and run_block <75.0 and pass_block < 78.0 and snap_accuracy >= 83.0 :
		position = Position.LONG_SNAPPER```

and I’ve got the other code:

but I still don’t have players populating in the Inspector. Am I missing a step? There are no error messages kicking back to me. It just doesn’t seem to be connecting.

Try getting my code working first, because I actually tested it (hence the screen shots). Then, once you have that working, use your code and tracking down the problem should be simple.