Idea For A Game - Nation Simulator

Question, will the game have hero units? Also, do you want the game to be realistic, have a little bit of fantasy, or fantasy? @A-bot_Moonblade

My idea

Graphic

  • It would be 3D.
  • The game will have simple graphics for the people.

Game Play

  • Players can choose to start in a template or a randomly generated map.
  • Players can change the difficulty by lowering or raising different things such as NPC Nations and tribes.
  • The game will be both single and multi player. I will develop the single player part first and if it is good, I will make multi player.
  • Players can choose their government and culture types. Player’s units will change appearances based on the culture types.
  • Players can upgrade and research new technologies and make people work or fight.
  • The player’s nations can not become too advanced(No laser guns or giant mechs).
  • The game will be realistic and the only fantasy things in the game will be Heliorite, Hexite, Luxite, brutes(Large and strong troops), etc.

If you have questions about the idea, please feel free to ask. I will add more ideas when I think of them. @A-bot_Moonblade

@ArtieMars-OG_Mars @Mostlime

1 Like

I thought you were the designer my man, what’s the point in your involvement if you’re going to make us design the game for you?

oh, and because I forgot to mention any of my capabilities, here’s 416 lines of code for my work-in-progress fps game’s player code (by far not done, still very buggy).

Programmed all in gdscript with Godot.

extends CharacterBody3D

@onready var head: Node3D = get_node("Head")
@onready var camera: Camera3D = get_node("Head/MainCam")
@onready var weaponCamera: Camera3D = get_node("CanvasLayer/SubViewportContainer/SubViewport/WeaponCam")
@onready var hitscanRay: RayCast3D = get_node("Head/HitCast")
@onready var standingCol: CollisionShape3D = get_node("StandingCol")
@onready var slidingCol: CollisionShape3D = get_node("SlidingCol")
@onready var weaponHolder: Node3D = get_node("Head/WeaponHolder")

## The angle, in radians, where the camera is confined to.
@export var CAMERA_CLAMP_ANGLE := 1.4

## The velocity that the player attains while sliding
@export var SLIDING_VELOCITY := 24.0

## The height (in metres) that the camera is at while standing
@export var STANDING_CAM_POS := 3.0

## The height (in metres) that the camera is at while sliding
@export var SLIDING_CAM_POS := 1

## Amount of time a dash takes, in seconds
@export var DASH_TIME := 0.1

## Player speed
@export var SPEED := 16.5

## Player speed when in the air
@export var AIR_ACCELERATION := 20

## Jump velocity
@export var JUMP_VELOCITY := 25

## The velocity at which the player groundslams at
@export var GROUNDSLAM_VELOCITY := 100.0

## The velocity of a dash
@export var DASH_VELOCITY := 49.5

## The amount of stamina regained per second
@export var STAMINA_REGEN_AMOUNT := 0.7

## Amount of which camera sways left and right
@export var CAMERA_SWAY_AMOUNT := 0.05

## Speed of which camera sways left and right
@export var CAMERA_SWAY_SPEED := 10.0

## The amount by which the weapon sways vertically
@export var WEAPON_VERT_SWAY_AMOUNT := 0.004

## Amount that weapon bobs
@export var WEAPON_BOB_AMOUNT := 0.1

## Frequency of a weapon's bobbing
@export var WEAPON_BOB_FREQUENCY := 0.01

## Time the player is given after a groundslam to slam bounce
@export var SLAM_BOUNCE_TIME := 0.2

## The speed that velocity decays at
@export var SLIDING_VELOCITY_DECAY_RATE: float

## How well the player can control themselves while sliding. In m/s.
@export var SLIDING_CONTROL_VELOCITY := 5.0

## Time a groundslam can last before it starts decreasing in time
@export var SLAM_BOUNCE_DEFAULT_TIME := 0.5

## Intensity of player's friction
@export var FRICTION_INTENSITY := 0.01

## If the player's velocity ends up any lower than this, it will be turned to zero
@export var VELOCITY_CUTOFF := 0.1

## Intensity of the player's air resistence
@export var AIR_RESISTENCE_INTENSITY := 0.5

## The upwards velocity given to the player when they jump while sliding
@export var SLIDE_JUMP_VERTICAL_VELOCITY := 16.0

## Horizontal velocity given to the player when they wall jump
@export var WALL_JUMP_HORIZONTAL_VELOCITY := 23.53

## Vertical velocity given to the player when they wall jump
@export var WALL_JUMP_VERTICAL_VELOCITY := 21.0

## Maximum time a player can wallcling before they lose grip and fall
@export var MAX_WALLCLING_TIME := 3.0

## The curve used to calculate how quickly they lose grip
@export var WALL_CLING_CURVE: Curve

var is_sliding: bool
var is_groundslamming: bool
var is_dashing: bool
var is_wall_clinging: bool

var increment_groundslam_timer: bool
var play_bounce_timer: bool

var direction_of_slide: Vector2
var direction_of_dash: Vector3

var wall_cling_time := 0.0
var dash_timer := 0.0
var ground_slam_time := 0.0
var slam_bounce_timer := 0.0
var def_weapon_holder_pos: Vector3
var stamina := 3.0

@onready var mouseSens: float = GameData.Settings.get_value("Controls", "mouseSensitivity")

func _ready() -> void:
	# When game runs, make mouse invisible + locked
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
	def_weapon_holder_pos = weaponHolder.position

func _input(event: InputEvent) -> void:
	if(event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED):
		rotate_y(-event.relative.x * mouseSens / 20000)
		head.rotate_x(-event.relative.y * mouseSens / 20000)
		head.rotation.x = clamp(head.rotation.x, -CAMERA_CLAMP_ANGLE, CAMERA_CLAMP_ANGLE)

func _process(delta: float) -> void:
	# This is here to make sure our weaponCam stays with the mainCam
	weaponCamera.set_global_transform(camera.get_global_transform())

func _physics_process(delta: float) -> void:
	if(Input.is_action_just_pressed("pause")):
		Pause()
	
	# Get the input direction
	var input_dir := Input.get_vector("left", "right", "front", "back")
	var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	
	# Add the gravity.
	if(not is_on_floor()):
		velocity += get_gravity() * delta
	
	Movement(direction, delta, input_dir)
	StaminaGen(delta) # Generates stamina to give to the player
	CamTilt(input_dir.x, delta) # Camera leans left or right depending on the input direction.
	WeaponLag(delta) # Makes the weapon move delayed to the player, adding weight
					 # and making it feel more realistic
	WeaponBobbing(delta) # Self explanatory, adds a bobbing anim to the weapon
	
	move_and_slide()

func Pause() -> void:
	# The pause function is very empty atm because I have not yet implemented a
	# pause menu, this code is simply for debugging purposes.
	
	# Unlocks the mouse
	Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func Movement(direction: Vector3, delta: float, input_dir: Vector2) -> void:
	# All of this movement code is begging for a rework, and it is still in the process
	# of being reworked.
	
	# Following code will only run when player is on the floor
	if(is_on_floor()):
		# Resets the timer, I'm thinking of other places this might be better of going,
		# for performance reasons, I don't like resetting the timer for every frame
		# that runs, it feels like a waste of cpu resources.
		wall_cling_time = 0.0
		
		if(Input.is_action_just_pressed("jump")):
			Jump()
		
		DirectionalMovement(direction) # Code that allows player to move on the ground.
		
		GroundslamStop() # Resets groundslam timer
		Sliding(direction, delta) # am working on reworking this
		
	# Following code only runs when the player is in the air.
	elif(not is_on_floor()):
		
		DirectionalAirMovement(delta, direction) # Code that allows player to move in mid-air.
		
		
		if(Input.is_action_just_pressed("jump") and is_on_wall()):
			WallJump()
		
		# If the player is falling downwards and is next-to a wall, start wall clinging.
		if(is_on_wall() and velocity.y <= 0.0):
			WallCling(delta)
		
		Groundslam() # Just like slide code, am working on reworking.
	
	
	# Following code runs while the player is both in the air or not.
	
	SlidingStop() # Disables sliding when player isn't sliding
	GroundslamTimer(delta) # Continuous loop that incrememnts groundslam timer
	Dashing(direction, delta) # Player can dash while in air or not

func DirectionalMovement(direction: Vector3) -> void:
	# Player moves linearly.
	velocity.x = direction.x * SPEED
	velocity.z = direction.z * SPEED

func DirectionalAirMovement(delta: float, direction: Vector3) -> void:
	# Unlike ground movement, air movement accelerates and decelerates.
	velocity.x += direction.x * AIR_ACCELERATION * delta
	velocity.z += direction.z * AIR_ACCELERATION * delta
	
	# TBH I don't like this code, but it's only here for debugging purposes. Will replace.
	# Keeps the mid-air velocity within the value specified in SPEED.
	velocity.x = clamp(velocity.x, -SPEED, SPEED)
	velocity.z = clamp(velocity.z, -SPEED, SPEED)

func Jump() -> void:
	# This code is VERY non-functional, I have yet to find a reasonable formula to do as I like.
	# This WILL be replaced, as a very important feature will not work as intended unless I replace
	# it.
	if(slam_bounce_timer > 0.0):
		if(ground_slam_time <= SLAM_BOUNCE_DEFAULT_TIME):
			velocity.y = JUMP_VELOCITY + (ground_slam_time * 15)
		else:
			velocity.y = JUMP_VELOCITY + (ground_slam_time * 16) - (ground_slam_time * 15)
	else:
		# If the player is not sliding, jump as usual. If the player IS sliding, cancel the slide
		# and add horzontal velocity into it with a smaller vertical velocity. Mid-slide jump does not work.
		if(not is_sliding):
			velocity.y = JUMP_VELOCITY
		else:
			velocity.x = velocity.x
			velocity.z = velocity.z
			velocity.y = SLIDE_JUMP_VERTICAL_VELOCITY
			is_sliding = false
			
		ground_slam_time = 0.0 # Resets timer
	
	is_sliding = false

func WallJump() -> void:
	# Makes the player jump upwards when wall jumping.
	velocity.y = WALL_JUMP_VERTICAL_VELOCITY
	
	# Finds the direction away from the wall and add horizontal velocity that way.
	velocity.x = get_wall_normal().x * WALL_JUMP_HORIZONTAL_VELOCITY
	velocity.z = get_wall_normal().z * WALL_JUMP_HORIZONTAL_VELOCITY

func WallCling(delta: float) -> void:
	# If the player has been wall clinging for less than the max time (default is 3 seconds)
	# then run the following code.
	if(wall_cling_time <= MAX_WALLCLING_TIME):
		# This code gets data from a graph, you can't see it in the text-editor but
		# it's basically just a bezier curve. The longer the player stays wallclinging, their effect
		# slowsly wears off and the player starts sliding down the wall instead (by the time 
		# the player wall clings for the max amount of time, they'll be falling at normal speed.)
		velocity.y = velocity.y * WALL_CLING_CURVE.sample(wall_cling_time / MAX_WALLCLING_TIME)
		is_wall_clinging = true
		wall_cling_time += delta # Incriments wall cling timer

func WeaponLag(delta: float) -> void:
	# I don't know how I made this work, don't ask me.
	weaponHolder.global_position = lerp(weaponHolder.global_position, head.get_node("WeaponPosY").global_position - velocity * WEAPON_VERT_SWAY_AMOUNT, 10 * delta)

func WeaponBobbing(delta: float) -> void:
	# If the player is moving, is on floor, and is not dashing nor sliding, run the code.
	if(velocity.length() > 0 and is_on_floor() and !is_dashing and !is_sliding):
		# Uses a sine curve to make the weapon procedurally bob.
		weaponHolder.position.y = lerp(weaponHolder.position.y, def_weapon_holder_pos.y + sin(Time.get_ticks_msec() * WEAPON_BOB_FREQUENCY) * WEAPON_BOB_AMOUNT, 10 * delta)
		weaponHolder.position.x = lerp(weaponHolder.position.x, def_weapon_holder_pos.x + sin(Time.get_ticks_msec() * WEAPON_BOB_FREQUENCY * 0.5) * WEAPON_BOB_AMOUNT, 10 * delta)
		
	else:
		# Transitions the weapon back to its idle position.
		weaponHolder.position.y = lerp(weaponHolder.position.y, def_weapon_holder_pos.y, 10 * delta)
		weaponHolder.position.x = lerp(weaponHolder.position.x, def_weapon_holder_pos.x, 10 * delta)

func CamTilt(input_x: float, delta: float) -> void:
	# Sways camera by given amount depending on the direction
	camera.rotation.z = lerp(camera.rotation.z, -input_x * CAMERA_SWAY_AMOUNT, CAMERA_SWAY_SPEED * delta)

func StaminaGen(delta: float) -> void:
	# Stamina does not regenerate while sliding, and the max available to carry is
	# 3.0 stamina.
	if(not is_sliding and stamina < 3.0):
		stamina += STAMINA_REGEN_AMOUNT * delta
	
	# If the incrimenting goes wrong and adds a little extra (making it more than
	# 3.0) then it will set it to the max, 3.0.
	stamina = clamp(stamina, 0.0, 3.0)

func SlidingStop() -> void:
	# Checks if the wrong collision is enabled or the head position is at an
	# incorrect place.
	if((standingCol.disabled or head.position.y == SLIDING_CAM_POS) and !is_sliding):
		
		# Resets the currently enabled collision
		standingCol.disabled = false
		slidingCol.disabled = true
		
		# Moves head back up to normal.
		head.position.y = STANDING_CAM_POS

func Sliding(direction: Vector3, delta: float) -> void:
	if(Input.is_action_just_pressed("slide") and is_on_floor()):
		print("Started sliding")
		
		# This detirmines the direction that the player is facing
		if(direction):
			direction_of_slide = Vector2(direction.x, direction.z)
		else:
			direction_of_slide = Vector2(
				-head.get_global_transform().basis.z.x,
				-head.get_global_transform().basis.z.z).normalized()
		is_sliding = true
		
		# These two change the collision from standing to 
		# sliding
		standingCol.disabled = false
		slidingCol.disabled = true
		
		# Changes the head's position to the sliding position
		head.position.y = SLIDING_CAM_POS
	if(not Input.is_action_pressed("slide")):
		
		is_sliding = false
		
		# These two change the collision from sliding to
		# standing
		standingCol.disabled = false
		slidingCol.disabled = true
		
		# Changes the position of the head to  the standing position
		head.position.y = STANDING_CAM_POS
	
	
	if(is_sliding):
		var last_pos: Vector3
		
		if(velocity.length() > SLIDING_VELOCITY):
			# Have not tested if this works, supposed to gradually lower velocity while sliding
			# if the player started sliding with a higher velocity.
			velocity.x = lerp(velocity.x, SLIDING_VELOCITY * direction_of_slide.x, SLIDING_VELOCITY_DECAY_RATE * delta) + (direction.x * SLIDING_CONTROL_VELOCITY)
			velocity.z = lerp(velocity.z, SLIDING_VELOCITY * direction_of_slide.y, SLIDING_VELOCITY_DECAY_RATE * delta) + (direction.x * SLIDING_CONTROL_VELOCITY)
		else:
			# Slides at a given velocity at a given direction, allows for limited movement
			# left and right.
			velocity.x = SLIDING_VELOCITY * direction_of_slide.x + (direction.x * SLIDING_CONTROL_VELOCITY)
			velocity.z = SLIDING_VELOCITY * direction_of_slide.y + (direction.z * SLIDING_CONTROL_VELOCITY)
		
		# Does not work. Supposed to stop sliding if it detects that the player's movement is
		# blocked/they cannot move anymore
		if(last_pos != Vector3.ZERO and last_pos.distance_to(global_position) < 5):
			is_sliding = false
		last_pos = global_position

func GroundslamTimer(delta: float) -> void:
	if(increment_groundslam_timer):
		ground_slam_time += delta
	
	if(play_bounce_timer):
		slam_bounce_timer += delta
		if(slam_bounce_timer >= SLAM_BOUNCE_TIME):
			slam_bounce_timer = 0.0
			play_bounce_timer = false

func Groundslam() -> void:
	if(Input.is_action_just_pressed("slide") and not is_on_floor()):
		print("Groundslammed")
		
		increment_groundslam_timer = true
		is_groundslamming = true
		# Makes the player constantly move downward
		velocity.y = -GROUNDSLAM_VELOCITY

func GroundslamStop() -> void:
	# If the function is called, but the is_groundslamming variable is true,
	# it fixes stuff by turning the variable false as well as a couple others.
	# Basically, it stops the groundslamming
	if(is_groundslamming):
		increment_groundslam_timer = false
		play_bounce_timer = true
		is_groundslamming = false

func Dashing(direction: Vector3, delta: float) -> void:	
	# If the player has enough stamina, dash.
	if(Input.is_action_just_pressed("dash") and stamina >= 1.0):
		stamina -= 1.0
		
		print("Dashed")
		if(direction):
			# If the player is moving, store the direction of movement in a variable
			direction_of_dash = Vector3(direction.x, 0, direction.z)
		else:
			# If player is not moving, use a default "forward" direction to dash
			direction_of_dash = Vector3(
				-head.get_global_transform().basis.z.x, 
				0,
				-head.get_global_transform().basis.z.z).normalized()
		is_dashing = true # enables dashing
	
	if(is_dashing):
		# Increment timer and move player in direction of dash at dash_velocity
		velocity = DASH_VELOCITY * direction_of_dash
		dash_timer += delta
	
	if(dash_timer >= DASH_TIME):
		# By default the dash should last 1 second, which is why we increment a timer for the dash.
		# We check if the timer has reached one second, if not: continue dashing. If true:
		# abandon the dash and reset the timer.
		is_dashing = false
		dash_timer = 0.0 # Reset the timer

func _on_piercing_revolver_primary_shot(damage: float) -> void:
	# Very basic shooting code for debugging purposes. This is not all the shooting code,
	# as there is another file/node dedicated for the guns/weapons with a lot more ACTUAL shooting
	# code.
	print("Shot!")
	if(hitscanRay.is_colliding()):
		print("Shot collided with obstacle")
4 Likes

W Mostlime

I am
I just said that cause darklord asked lol
W code though

1 Like

reads none of it
yeah, nice ideas

1 Like

Answer this question @A-bot_Moonblade

realistic

1 Like

I think I am not going to be chosen…

1 Like

I think I should watch some videos about Unity or Mostlime will be chosen.

1 Like

At least Artie will not be chosen either.

1 Like

yeah do that
yall still have a month

1 Like

I really should, because I bought a lot of courses about Unity on Udemy but I have not watched any of them.

Use JavaScript or Java because I’m learning those.

1 Like

I am going to use Unity because I am learning it. But I should relearn Java.

1 Like

the developer can use whatever language they are comfortable with that makes the game good

wat is this talk abt codin

update: I am working on refactoring this entire thing into a state machine, which would make it far easier to expand, far cleaner & simpler, and so much more beautiful. I am testing state machines and component-based/composition-based programming in a side project of mine, and it seems great.

edit: also, changing this to a statemachine means every bit of functionality would be split into different files and nodes, like shown in the ss below (this is from my side-project)
image

edit 2:
The state machine node itself is like a manager, here’s the code for it:

extends Node

@export var starting_state: State
var current_state: State

func init(parent: CharacterBody2D, move_interface: MovementInterface) -> void:
	for child in get_children():
		child.parent = parent
		child.move_interface = move_interface
	
	change_state(starting_state)

func change_state(new_state: State) -> void:
	if current_state:
		current_state.exit()
	
	current_state = new_state
	current_state.enter()

func process_physics(delta: float) -> void:
	var new_state := current_state.process_physics(delta)
	if new_state:
		change_state(new_state)

func process_input(event: InputEvent) -> void:
	var new_state := current_state.process_input(event)
	if new_state:
		change_state(new_state)

func process_frame(delta: float) -> void:
	var new_state := current_state.process_frame(delta)
	if new_state:
		change_state(new_state)

and this is the class file for each State:

class_name State
extends Node

var parent: CharacterBody2D
var move_interface: MovementInterface

func enter() -> void:
	pass
	
func exit() -> void:
	pass

func process_physics(delta: float) -> State:
	return null

func process_input(event: InputEvent) -> State:
	return null

func process_frame(delta: float) -> State:
	return null

note: the “pass” and “return null” are meant to be replaced in each state. The enter function is called the first frame the state is enabled, and the exit state the last frame. Process physics is for movement/physics calculations, input is for input, process frame is for anything else.

The variables “parent” and “move_interface” do not have to be initialised in any of the states, since each state is inherited from this class.

Here is an example of a state, taken from my “move” state:

extends State

@export var IDLE_STATE: State
@export var DASH_STATE: State

func enter() -> void:
	pass

func exit() -> void:
	pass


func process_input(event: InputEvent) -> State:
	if move_interface.wants_dash():
		return DASH_STATE
	if !move_interface.get_movement_input():
		return IDLE_STATE
	return null

func process_physics(delta: float) -> State:
	parent.velocity = parent.SPEED * move_interface.get_movement_input()
	parent.move_and_slide()
	
	return null

note: input functions can be done in process_physics(), but I do it in process_input() because it’s cool and sigmer. The variables at the top can be assigned in-editor, by dragging and dropping their respective states (@export variables in godot can be modified outside the code-editor in the gui editor, useful for quickly modifying or configuring things, see the ss)
image

Also, in the code you can see references to “move_interface”, move_interface is a separate component/node, added as part of my efforts for a composition style of programming. Here’s the code for it:

extends MovementInterface

func get_movement_input() -> Vector2:
	var input_dir := Input.get_vector("left", "right", "up", "down")
	var direction := input_dir.normalized()
	
	return direction

func wants_dash() -> bool:
	return Input.is_action_just_pressed("dash")

MovementInterface is also its own class, so that I can make several versions of this for different entities, like AI enemies. This is why I used composition instead of directly having input checks in the state machine’s code, so that I can copy the statemachine everywhere with few changes, and all I’d need to change is the movement interface itself.

3 Likes

I think I might not do this.