How can I align my troops on a battle screen? - 2d

I'm trying to recreate Stickwar's flash game, a game in which I'm a big fan of but got suck when I attempt to align my troops. Particularly, here is the in game views of it:
As you can see, the are many stickmans who align next to each other. A horizon line is consist of five stickmans. If there is another stickman, it will automatically aligns on the next line and so on.
These Stickmans, however, do not always function like that. The stickman with archer's class will always stay behind all of the troops, while the guy with spear are always if in the front of the troops as leader. If the leader was spawn but the first line is full, then the other troops will automatically move back, leaving the first line empty, which is the same with the second lines and so on.
I have no idea how to build this up in Godot. So if there are anybody who have some hints, please let me know. I really appreciate all the efforts. This doesn't seems like a good question to ask but I honestly can't find any Godot tutorials related to it, so I'm really sorry if this question is bother you. (also sorry for my bad grammars, too)

And don't expect to find tutorials for everything. How does people who make the tutorials figure out things in the first place? What you need is to break this down into smaller problems that are addressable.
I'll figure it out as I write.
One part is that you need to have units that can move to a particular location. Which could be to attack an enemy, or, yes, to their spot in the formation.
Another is that you need to define the possible spot where the units can stand in formation.
And the last is that you need code that tells the units to which spot they should go. This code would make sure to not send multiple units to the same spot, and to follow whatever rules for the formations you have.
Given the game, I'm going to use Area2D for the units.
Why?
I'm not using Sprite or something graphic directly because they must be clickable by the player.
I'm not using KinematicBody2D nor RigidBody2D because I don't see the collide with each other.
And I'm not using StaticBody2D because they need to move freely.
So Area2D it is.
Now, these Area2D will be given a target to go to. We can use a Vector2 for that:
extends Area2D
export var target:Vector2
Let us make sure that the target matches their position when they are ready (And I remind you that Vector2.ZERO is a valid position):
extends Area2D
export var target:Vector2
func _ready() -> void:
target = global_position
I'm working with global coordinates because I don't want to have to convert to local coordinates of the unit when I write the code that will set these positions.
I remind you that the local space of different nodes is different.
Now, we need it to move towards that target. So we write _physics_process. Let us start with direction and distance:
extends Area2D
export var target:Vector2
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var direction := global_position.direction_to(target)
var distance := global_position.distance_to(target)
For reference, I could have also done it like this:
extends Area2D
export var target:Vector2
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var displacement := target - global_position
var direction := displacement.normalized()
var distance := displacement.length()
And let us figure out how much we can move per physics frame. Which also means I need to define a speed:
extends Area2D
export var target:Vector2
export var speed:float = 100.0
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var direction := global_position.direction_to(target)
var distance := global_position.distance_to(target)
var max_distance := speed * delta
Ok, I'm setting the speed to 100.0 because - given that we are working in 2D - it will be in pixels per second. And if it is something too small you are not going to see it. However, tweak the value to your needs.
And for the distance we can move in the current physics frame I'm multiplying speed by delta. Remember that delta is the elapsed time since the last frame, and also remember that speed is distance divided by time. So when we multiply the speed by time, the time cancels out and we have distance.
Now, we don't want to overshoot the target, so we are going to clamp the distance:
extends Area2D
export var target:Vector2
export var speed:float = 100.0
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var direction := global_position.direction_to(target)
var distance := min(global_position.distance_to(target), speed * delta)
And we need to move, of course:
extends Area2D
export var target:Vector2
export var speed:float = 100.0
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var direction := global_position.direction_to(target)
var distance := min(global_position.distance_to(target), speed * delta)
global_position += direction * distance
Just for reference I'll write the code for KinematicBody2D using move_and_slide:
extends KinematicBody2D
export var target:Vector2
export var speed:float = 100.0
func _ready() -> void:
target = global_position
func _physics_process(delta:float) -> void:
var direction := global_position.direction_to(target)
var distance := global_position.distance_to(target)
var velocity = direction * min(distance / delta, speed)
move_and_slide(velocity)
Again, speed is distance divided by time. And velocity is speed with a direction. I hope this makes sense.
We would also need to trigger animations and flip the sprite (which would be a child node) if necessary.
Something like this:
$AnimatedSprite.flip_h = direction.x < 0
if distance.is_equal_approx(Vector2.ZERO):
$AnimatedSprite.play("idle")
else:
$AnimatedSprite.play("walk")
About defining the posible spots, we could use Position2D. Perhaps group them as necessary.
However, as I'll describe below, we can use Path2D.
And to assign the targets...
We are going to start from the front line and move backwards. We are going to need to have the units in buckets, and exhaust the buckets in order.
Ok, there are multiple kinds of units. You can make an scene for each kind of unit, they can all use the script for motion that I have above. You can then also go ahead an set different node groups to them, so we can get them with get_tree().get_nodes_in_group.
Now, we have different rules for how each group uses each line. If I understand correctly, the leader will be alone on its line, soldiers will be in groups of 5, and archers will be all in the same line.
Thus, it would be useful to define the lines as... lines. I would say Line2D but that is graphical and we don't want these visible. So, instead we will use Path2D, which also has some convenient methods to find equally spaced points over them.
For convenience, let us make the Path2D children of the node where we will be assigning targets. So we can get them like this:
var paths:Array = get_children()
We also need the list of groups, for example:
var groups = ["Leader", "Soldier", "Archer"]
And the number of them we can group… Let us make a class (I suggest to put this at the end of the file).
class Group:
var name:String
var max_count:float
func _init(name:String, max_count:float):
self.name = name
self.max_count = max_count
Now the list of groups:
var groups = [
Group.new("Leader", 1),
Group.new("Soldier", 5),
Group.new("Archer", INF)
]
And we will be working down these in order:
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
if units_in_group.empty():
continue
var path := paths.pop_back()
var count := min(units_in_group.size(), group.max_count)
# figure out the positions
Ok, now I have another issue. I have a range from 0 to 1 to divide by n elements. When I have one element I want to get the position at the middle (0.5), but if I say 1/n when n is 1 I get 1 not 0.5.
So, 1/n will be the separation between elements. But I need to offset the position by half that.
I think I got it (I'm writing here, not testing it):
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
if units_in_group.empty():
continue
var path := paths.pop_back() as Path2D
var count := min(units_in_group.size(), group.max_count)
var separation := 1.0/count
var offset := separation * 0.5
for index in count:
var position := path.interpolatef(index * separation + offset)
# assign the position
I must be getting the position in the local space of the path. Under that assumption, I'll convert them to global before passing them to the unit.
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
if units_in_group.empty():
continue
var path := paths.pop_back() as Path2D
var count := min(units_in_group.size(), group.max_count)
var separation := 1.0/count
var offset := separation * 0.5
for index in count:
var unit := units_in_group[index]
var position := path.to_global(
path.interpolatef(
index * separation + offset
)
)
unit.target = position
Ah, but wait, we need to continue assigning units from the same group on the next path…
Let us start by erasing from units_in_group the units we have already assigned a target to, so we don't repeat. Which also means we cannot get them by index. So I will use pop_back instead:
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
if units_in_group.empty():
continue
var path := paths.pop_back() as Path2D
var count := min(units_in_group.size(), group.max_count)
var separation := 1.0/count
var offset := separation * 0.5
for index in count:
var unit := units_in_group.pop_back()
var position := path.to_global(
path.interpolatef(
index * separation + offset
)
)
unit.target = position
And now we can loop until it is empty:
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
while not units_in_group.empty():
var path := paths.pop_back() as Path2D
var count := min(units_in_group.size(), group.max_count)
var separation := 1.0/count
var offset := separation * 0.5
for index in count:
var unit := units_in_group.pop_back()
var position := path.to_global(
path.interpolatef(
index * separation + offset
)
)
unit.target = position
And I think that should work.
Perhaps we could do better… In particular, I would rather this assigned the unit that is nearest to the position we want.
We can find the best unit like this:
var position := path.to_global(
path.interpolatef(
index * separation + offset
)
)
var best_distance := INF
var best_unit:Node2D = null
for unit in units_in_group:
var c_unit := unit as Node2D
var c_distance := c_unit.global_position.distance_to(position)
if c_distance < best_distance:
best_distance = c_distance
best_unit = c_unit
Here c_ stands for current. I would normally write it down fully, but I opted for this to not make the lines too long.
Since we are going to be picking this way, we cannot simply pop_back from units_in_group. Instead we will erase the one we picked.
var paths:Array = get_children()
for item in groups:
var group := item as Group
var units_in_group := get_tree().get_nodes_in_group(group.name)
while not units_in_group.empty():
var path := paths.pop_back() as Path2D
var count := min(units_in_group.size(), group.max_count)
var separation := 1.0/count
var offset := separation * 0.5
for index in count:
var position := path.to_global(
path.interpolatef(
index * separation + offset
)
)
var best_distance := INF
var best_unit:Node2D = null
for unit in units_in_group:
var c_unit := unit as Node2D
var c_distance := c_unit.global_position.distance_to(position)
if c_distance < best_distance:
best_distance = c_distance
best_unit = c_unit
units_in_group.erase(best_unit)
best_unit.target = position
I'm tempted to refactor the selection of the best unit to a separate function. I submit it to your consideration.
Finally, thinking about the failure modes of this code: What comes to mind is that there might not be enough paths to position all the units. You can prevent that by setting a maximum number of units… Or you could write code to create paths as needed.
Hopefully I addressed everything that needed to be addressed.

Related

Move "forwards" when KiematicBody is rotated with GDScript

I have a very basic third person controller:
func _physics_process(delta):
vel.x = 0
if Input.is_action_pressed("move_forward"):
vel.x += SPEED * delta
if Input.is_action_pressed("turn_left"):
rotate_y(deg2rad(ROTATION_INTENSITY))
move_and_collide(vel)
However on rotating and then moving forward, the player will move forward on the x axis instead of the direction the player is pointed. How do i fix this?
Thanks in advance!
Your KinematicBody is an Spatial, and as such it is positioned by a Transform.
You can get the Transform from the parent Spatial (or the root of the scene tree if there is no parent Spatial) from the transform property.
And you can get the Transform from root of the scene directly from the global_transform property.
The Transform has two parts:
A Vector3 called origin.
A Basis called basis.
Together they encode a coordinate system. In particular basis has three vectors that hold the direction and scale of each of the axis of the coordinate system. And origin has the position of the origin of the coordinate system.
Now, Godot's coordinate system in 3D is right handed with UP = Y and FORWARD = -Z. You can confirm this with the constants Vector3.UP and Vector3.FORWARD. Also notice this differs from Unity and Unreal which are left handed.
Ergo, if you want the direction of one of the axis, you can get it form the basis of the global transform. Like this:
var forward := -global_transform.basis.z
var backward := global_transform.basis.z
var left := -global_transform.basis.x
var right := global_transform.basis.x
var down := -global_transform.basis.y
var up := global_transform.basis.y
You could use those to build your velocity in global coordinates, for example:
velocity += global_transform.basis.x * SPEED
Alternatively, you can convert vectors from local to global space with to_global (and from global to local with to_local) so you can do this:
var forward := to_global(Vector3.FORWARD)
var backward:= to_global(Vector3.BACKWARD)
var left := to_global(Vector3.LEFT)
var right := to_global(Vector3.RIGHT)
var down := to_global(Vector3.DOWN)
var up := to_global(Vector3.UP)
This also means you may build your velocity in local coordinates, and convert it to global coordinates at the end:
move_and_slide(to_global(velocity))
And just to be clear, move_and_slide expects global coordinates.
I will also point out that it is possible to rotate a vector with rotated and project a vector onto another with project.

Basic Calculator: How to script a function for deleting digits one by one? (GDScript)

How am I supposed to code a button that deletes digits in a basic calculator one by one?
Heres my script so far: https://pastebin.com/xLj2iCHU
func _on_BDeleteDigit_pressed():
var numberAdded
var numberBefore = $NumberStuffs.get_text()
numberAdded = numberAdded.length() - 1
numberAdded[numberBefore]=("")
$Outputlabel.set_text(str(numberAdded))
This is also the error when the delete button is pressed:
https://i.stack.imgur.com/8OJTI.png
Explaining the error
I'll start with the error. It says
Nonexistent function 'length' in base 'Nil'
The error is telling you that you are trying call a function called length on something that is Nil, but Nil does not have a function called length.
The problem is that you are trying to call something on Nil. What are you trying to call? length. So where is that? That is here:
numberAdded = numberAdded.length() - 1
So, numberAdded is Nil. The next question is why. So, let us see where numberAdded got its value…
So you declare it here:
var numberAdded
Then we have this line that has nothing to do with it:
var numberBefore = $NumberStuffs.get_text()
And then the line with the error:
numberAdded = numberAdded.length() - 1
What did you expect numberAdded.length() to be, if numberAdded does not have a value? Of course that is an error.
By the way, the code suggests that you expected numberAdded to be, well, a number. Which makes me wonder what you expected from this:
numberAdded[numberBefore]=("")
Yes you can do the inverted indexing thing in C or C++, because it is just adding pointers. But not GDScript, you don't.
I suspect that using types would help you. For example declare numberAdded to be an integer like this: var numberAdded:int. Yes, GDScript has types, use them.
How to delete the last character
Let me take as reference how you add a digit:
func _on_Button1_pressed():
var currentNumber = $NumberStuffs.get_text()
currentNumber = currentNumber + "1"
$NumberStuffs.set_text(currentNumber)
Alright, we are storing a string on the text property of the node $NumberStuffs. And we add a digit by concatenating to it.
The above code should be is equivalent to this:
func _on_Button1_pressed():
$NumberStuffs.text += "1"
If get_text and set_text are custom functions in GDScript, you may want to look into setget.
Thus, the question is how to remove the last character of a string. There are several ways!
One way would be using erase:
func _on_BDeleteDigit_pressed():
var text := $NumberStuffs.text
var length := text.length()
text.erase(length - 1, 1)
$NumberStuffs.text = text
Note that erase is modifying the string, but the string is a copy we got from reading $NumberStuffs.text (or calling $NumberStuffs.get_text), which is why we need to set to $NumberStuffs.text (or call $NumberStuffs.set_text) anyway.
Or if you prefer:
func _on_BDeleteDigit_pressed():
var text := $NumberStuffs.get_text()
var length := text.length()
text.erase(length - 1, 1)
$NumberStuffs.set_text(text)
Alternatively, we might get a string which is like the one we have except lacking the last character. We can do that with left (which lets you get a portion of the string from the left, there is also a right function that does the same but from the right):
func _on_BDeleteDigit_pressed():
var length := $NumberStuffs.text.length()
$NumberStuffs.text = $NumberStuffs.text.left(length - 1)
Or, if you prefer:
func _on_BDeleteDigit_pressed():
var text := $NumberStuffs.get_text()
var length := text.length()
$NumberStuffs.set_text(text.left(length - 1))
In general we can get a string containing a portion of another string with substr:
func _on_BDeleteDigit_pressed():
var length := $NumberStuffs.text.length()
$NumberStuffs.text = $NumberStuffs.text.substr(0, length - 1)
Or, if you prefer:
func _on_BDeleteDigit_pressed():
var text := $NumberStuffs.get_text()
var length := text.length()
$NumberStuffs.set_text(text.substr(0, length - 1))
And yes, you can modify the string by indexing it, which I believe is what you were trying. For example:
$NumberStuffs.text[-1] = "a"
Which is equivalent to:
var length := $NumberStuffs.text.length()
$NumberStuffs.text[length - 1] = "a"
Which is equivalent to:
var text := $NumberStuffs.get_text()
var length := text.length()
text[length - 1] = "a"
$NumberStuffs.set_text(text)
Will replace the last character with an "a". And yes, you could remove by doing that. That would be:
$NumberStuffs.text[-1] = ""
Which is equivalent to:
var length := $NumberStuffs.text.length()
$NumberStuffs.text[length - 1] = ""
Which is equivalent to:
var text := $NumberStuffs.get_text()
var length := text.length()
text[length - 1] = ""
$NumberStuffs.set_text(text)
By the way, I have been using := when declaring the variables. That declares the variable of the type of whatever I'm using to initialize them.

Handle recursive function within an other function ocaml

If I have one or more recursive functions inside an Ocaml function how can I call them without exit from the main function taking their value as return of the main function?
I'm new in Ocaml so I'll try to explain me better...
If I have :
let function =
let rec recursive1 = ...
...
let rec recursive2 = ...
...
How can I call them inside function to tell it "Hey, do you see this recursive function? Now call it and takes its value."
Because my problem is that Ocaml as return of my functions sees Unit instead of the right return.
I will post the code below :
let change k v list_ =
let rec support k v list_ =
match list_ with
| [] -> []
| (i,value) :: tl -> if i = k
then (k,v) :: tl
else (i,value) :: support k v tl in
let inserted = support k v list_ in inserted
let () =
let k = [ (1,"ciao");(2,"Hola");(3,"Salut") ] in
change 2 "Aufwidersen" k
Change takes as input a key, a value and a (int * string )list and should return the same list of the input but changing the value linked to the key selected ( if in list ).
support, instead, makes the dirty job. It builds a new list and when k is found i = k it changes value and attach the tile, closing the function.
The return of change is unit when it should be (int * string) list. I think because inserted isn't taken as return of the function.
change does not return unit. The error in fact tells you exactly the opposite, that it returns (int * string) list but that it expects unit. And it expects unit because you're assigning it to a () pattern.
I don't know what you actually intend to do with the return value, as right now you don't seem to care about it, but you can fix the error by just assigning it to a name:
let result: (int * string) list =
let k = [ (1,"ciao");(2,"Hola");(3,"Salut") ] in
change 2 "Aufwidersen" k
Since it's not used I've added a type annotation to make sure we're getting what we expect here, as otherwise result could be anything and the compiler wouldn't complain. You don't typically need this if you're going to use result however, as you'd then get an error if the type doesn't unify with its usage.

F# map and distinct objects

I have some nondescript but distinct objects (specifically, unnamed variables in logic expressions) that I want to put in a map that associates them with their values. As I understand it, map needs to distinguish objects by some ordered field, so I can't just have
type Term =
...
| Var
as this would not allow different variables distinguishable from each other. Instead I could presumably have
type Term =
...
| Var of int64
and then have a new_var function that increments a global int64 counter and returns a new variable with the incremented value. This seems slightly inelegant, but should work.
Is the global counter the recommended way to handle this, or is there a more idiomatic method?
It's not really a "map having to distinguish objects" thing - when you declare a type like this:
type Term =
| Var
you have a type with a single valid value - Var. If you're saying you want to have objects that are distinct - this is not what you want. You can still use that type as a key in a map - not a particularly useful one though, since it will have at most a single element.
Using a counter is a good enough way to handle it. If you don't want a "global" one, you can roll it into a function using a ref cell to hold it:
type Term =
| Var of int
let make =
let counter = ref 0
fun () ->
counter := !counter + 1
Term.Var (!counter)
Or use GUIDs if you don't care about the values and want the counter out of the picture:
type Term =
| Var of System.Guid
let make () =
Term.Var (System.Guid.NewGuid())

OCaml - Map - how to "check" key and value?

How to create a Map that I will have a key: (int * int) and when it comes to key it is my_own_type ?
Here's a small example:
module IPMap = Map.Make(struct type t = int * int let compare = compare end)
let mymap = IPMap.add (0, 0) (my_value : my_own_type) IPMap.empty
let mymap' = IPMap.add (1, 2) (t: my_own_type) mymap
Note: you don't have to write (t: my_own_type). You can just write t. I'm including it just for emphasis.
When you create a map module like IPMap, you only need to specify the type of the keys. You can have as many different maps with different value types as you like.
Note 2: OCaml maps are immutable. I worry that you haven't fully grappled with this issue yet. (Apologies if I'm wrong.)

Resources