Ship, Captain, and Crew: Give it a spin!

Well, I suppose “roll” would be a more popular dice game term, but spin is actually more accurate in this case! I’m actually rotating the dice in a spin maneuver, and having them fall on the table, rather than rolling them across it from an angle or the side.

diceroll

The first question I had to tackle was what type of object to use for the game. The three types are Static Body, Kinematic Body, and Rigid Body. Each have their own uses, and here I put into words my limited understanding. Be sure to comment if you can clarify any of these.

Static Body is an object that is not supposed to react to any force. It should instead stay right where it is when it is struck by another object. In this game, I used a static body for the table top. This way the force of being struck by objects, such as the dice, and physics powers, like gravity, have no affect on it. In fact, I didn’t even give the table legs, because nothing is needed to “hold” the table up, since it is a static body.

Kinematic Body is an object that is often used for making the main player of a game, or something like that. It is not affected by physics unless you program it too. Gravity and such have no affect on it until you tell it to through code. So, if I had a kinematic ball roll across the table, when it gets to the edge, it would keep rolling on air, as if the table were still under it. This is what we used in BornCG’s great video game tutorial for the main player. It is very easy to control movement of this object.

Rigid Body is a type of object that by nature is fully affected by physics. Gravity and inertia all come into play on this type of object. If the table were not there, a rigid body object would fall indefinitely. These objects are difficult to move in one sense, because you can’t just say, “go that way” like you do with kinematic bodies, instead you have to exert force against them in a direction to cause them to move that way.

To make the dice game as realistic as possible, I used rigid bodies for the dice. When they fall to the table, they can bounce, tumble, or bump and shove other dice. The big question was how to move them. Of course, once in the air, they fall to the table and tumble, etc. That was the easy movement, because it is not programmed by me at all, it is simply the engine physics taking place. The tough movement was what to do with them after they hit the table.

Originally, I was planning to move them aside, and set them out of the way so I could roll the remaining dice. Not having any 3D experience, I thought this wouldn’t be too hard, but it became a nightmare for rigid bodies. Here are several methods I tried after reading hours of documentation and QA forums:

var oldPosition = get_node("LowPolyDiceC").get("dicePosition")
var targetPostion = get_node("pirateShip").get("shipPosition")
var newDirection = targetPostion-oldPosition
print(newDirection)
$LowPolyDiceC.apply_impulse(Vector3(),newDirection)
#translate(translation.direction_to(newDirection)) 
# This one only moves a tiny bit, and not in the right direction.   
#translation.direction_to(newDirection) 
# This one doesn't move at all.   
#apply_impulse(Vector3(),newDirection) 
# This one rockets it towards the ship, then it falls off the table.   
#translate(newDirection) 
# This one does not go to the ship, but goes up in the sky near it.

I found several ways to translate, or “teleport” the dice somewhere else, but not where I wanted them. And, once transported, they didn’t work with physics anymore. I also found ways to whack them with a force, but they arced up through the air and fell off the table (think golf club hitting golf ball). None of those would do. I tried asking my issue as a question on Godot’s QA forums, but I didn’t get a response from anyone, so I had to give this idea up.

Instead, I used the impulse (force) to send the dice straight up into the air again. Then, as they go up into the air, I perform several “spins” of the dice to randomize the result. Here’s my code:

func _make_dice_random (diceName):
	var x = randi() % 365 + 1
	var y = randi() % 365 + 1
	var z = randi() % 365 + 1
	get_node(diceName).rotate_x(deg2rad(x))
	get_node(diceName).rotate_y(deg2rad(y))
	get_node(diceName).rotate_z(deg2rad(z))

func _spin_dice():
	if tumbler > 0 :
		tumbler = tumbler - 1
		#print (tumbler)
		var upAmmount = 4
		if takenA != 1:
			$LowPolyDiceA.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
			_make_dice_random("LowPolyDiceA")
		if takenB != 1:
			$LowPolyDiceB.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
			_make_dice_random("LowPolyDiceB")
		if takenC != 1:
			$LowPolyDiceC.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
			_make_dice_random("LowPolyDiceC")
		if takenD != 1:
			$LowPolyDiceD.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
			_make_dice_random("LowPolyDiceD")
		if takenE != 1:
			$LowPolyDiceE.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
			_make_dice_random("LowPolyDiceE")
		$spinTimer.start()
	else :
		$spinTimer.stop()
		$Timer.start()
	
func _roll_dice():
	$ControlHUD/HUDrollButton.hide()
	remainingRolls = remainingRolls - 1
	get_node("ControlHUD/HUDrollsNumLabel").set("howManyRolls", remainingRolls)
	tumbler = 3
	var upAmmount = 6
	if takenA != 1:
		$LowPolyDiceA.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
	if takenB != 1:
		$LowPolyDiceB.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
	if takenC != 1:
		$LowPolyDiceC.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
	if takenD != 1:
		$LowPolyDiceD.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
	if takenE != 1:
		$LowPolyDiceE.apply_impulse(Vector3(),Vector3(0,upAmmount,0))
	$spinTimer.start()

It reads from the bottom up. First I call the _roll_dice() function to send any dice up that are not “taken” or in use by the player for their ship, captain, or crew. Then a timer is started, to allow the dice time to rise up off the table. Once the timer ends, it calls the _spin_dice() function, which has several calls to the _make_dice_random(dicename) function, picking random numbers of 365 degrees*. This is done as many times as the “tumbler” variable is set to, or in this case, 3 times. Since the dice are rotated on the x, y, and z axis, they get very randomly arranged, and then fall to the table, tumbling around to their final position.

Each dice has 6 small invisible cube areas on the faces of the dice that correspond to the opposing sides number. So, when they hit the table, that reports the number the dice has face up to the variables, so the dice number is set and known. That looks like this:

func _on_AreaA2_body_entered(body):
	if body.name == "TableObject" :
		$diceSoundPlayer.play()
		diceA = 2
		#print (diceA)

And finally, once the dice have come to rest, they are checked for their number:

func _check_all_dice():
	$Timer.stop()
	#print ("dice check")
	#print (diceA, diceB, diceC, diceD, diceE)
	# Check for Ship
	if hasShip == 0 :
		if diceA == 4 :
			hasShip = 1
			$pirateShip.show()
			takenA = 1
		elif diceB == 4 :
			hasShip = 1
			$pirateShip.show()
			takenB = 1
		elif diceC == 4 :
			hasShip = 1
			$pirateShip.show()
			takenC = 1
		elif diceD == 4 :
			hasShip = 1
			$pirateShip.show()
			takenD = 1
		elif diceE == 4 :
			hasShip = 1
			$pirateShip.show()
			takenE = 1
	# Now check for captain
	if hasShip == 1 :
		if diceA == 5 :
			hasCapt = 1
			$pirateHat.show()
			takenA = 1
		elif diceB == 5 :
			hasCapt = 1
			$pirateHat.show()
			takenB = 1
		elif diceC == 5 :
			hasCapt = 1
			$pirateHat.show()
			takenC = 1
		elif diceD == 5 :
			hasCapt = 1
			$pirateHat.show()
			takenD = 1
		elif diceE == 5 :
			hasCapt = 1
			$pirateHat.show()
			takenE = 1
	# Now check for crew
	if hasCapt == 1 :
		if diceA == 6 :
			hasCrew = 1
			$piratecrewHat.show()
			takenA = 1
		elif diceB == 6 :
			hasCrew = 1
			$piratecrewHat.show()
			takenB = 1
		elif diceC == 6 :
			hasCrew = 1
			$piratecrewHat.show()
			takenC = 1
		elif diceD == 6 :
			hasCrew = 1
			$piratecrewHat.show()
			takenD = 1
		elif diceE == 6 :
			hasCrew = 1
			$piratecrewHat.show()
			takenE = 1
	# Now check cargo
	if hasCrew == 1:
		if remainingRolls > 0:
			$ControlHUD/HUDkeepButton.show()
		else:
			$ControlHUD/HUDkeepButton.hide()
		_update_score(diceA + diceB + diceC + diceD + diceE - 15)
	else :
		_update_score(0)
	
	if remainingRolls > 0:
		$ControlHUD/HUDrollButton.show()
		if theCurrentPlayer == 0:
			$computerTimer.start()
	else :
		$ControlHUD/HUDrollButton.hide()
		$updateTimer.start()

This string of if/elif statements is really ugly, but gdScript doesn’t have a “switch” or “case” block method. It does have a “match” method, but I couldn’t understand how to implement it properly.

Hopefully that was interesting and or helpful to other newbies like myself. You can always check out the full source code for the game at my GitLab.

Linux – keep it simple.

* Yes, I know there are really only 360 degrees in a circle, but this actually improved the random factor, based on my research, because I was under the impression that the random number generator seemed to favor slightly lower numbers, so I added a few. If it goes over 360, it just keeps rotating, as if from 0.

Ship, Captain, and Crew: Sounds Good…

music

Well, one of those things every game needs is sounds. Not just sound effects, but also background music. It’s a really easy way to enhance the game experience without actually changing any part of the game mechanics. The only problem was, I didn’t know how to do it in Godot.

So, after reading several tutorials, and reading Godot Engine’s how to’s and Q/A’s, I started out by adding some music to my game. At first, per the guides, I just added a new child node to my game launch menu that was an AudioSamplePlayer, and added the music to it. Unfortunately, this worked great in the launch menu scene, but as soon as you changed scenes, such as going to the game table scene, the music would abruptly stop.

So I read several more Q/A’s and guides. The problem is, if you put something in a scene, it ends when the scene does. What I needed was a scene that would be always loaded and was outside of the normal game. Per the guide, I then made a scene consisting of just the AudioSamplePlayer, chose music for it, and saved it as it’s own scene, called MusicPlayer.tscn. Then I went to the project settings and added it to the auto loader when the game starts.

But it didn’t work. In fact, the whole game crashed.

Turns out, since my automatic global scaling script changed all the root nodes children’s scales to meet the screen size, it would crash trying to pull the rectangle size of the MusicPlayer scene (since it doesn’t have one). That was a bit frustrating. But, it turned out to be a simple fix. I changed the MusicPlayer scene to have a Control Node as the root node, and gave it the same size as every other scene in the game, 1920×1080. Then it would allow me to set it’s size, and the game would continue. Seemed a bit hacky to me, but to God be the glory, it worked!

Then there was the question of the dice sounds, when the dice hit the table. Granted, later I’ll be going over the dice mechanics code in the game, but for now, it was simple to add to each dice:

func _on_AreaA6_body_entered(body):
if body.name == “TableObject” :
$diceSoundPlayer.play()
diceA = 6
#print (diceA)

I simply added one audio node to the game table scene, called diceSoundPlayer, added my dice wave sound to it, and then started it with each collision between dice and table. It worked great!

Feel free to check out my project on my GitLab!

Linux – keep it simple.

Ship, Captain, and Crew: Scaling the Screen

diceGameScalescript

So, now that I’ve completed BornCG’s great 3D game tutorial, I’m ready to set out on my own! Of course, I already mentioned what the game would be about, it is a remake of the classic “Ship, Captain, and Crew” dice game. I’ve already made a 2D version of this game as an Android app, but now I’m trying to develop some 3D skills by remaking the game using Godot’s 3D engine.

I have several goals for this game:

  1. 3D – I know, it seems kind of obvious, but I never made a 3D game on my own before! (No tutorial, help, etc.)
  2. Featuring all of my own 3D models – I want to personally build each 3D model to enhance my limited Low Poly Blender skills.
  3. Cross platform – I would like to be able to play this game on a Linux, Windows, and Android OS.
  4. Open Source – It is important to me that this project is open sourced to the public for any to view, edit, copy, and use. Perhaps my meager and fledgling skills will help some other newbie to create something better than I did.

So, with that in mind, my first goal was to tackle screen size. Once before, I made a 2D side scroller called Critical Velocity using the libGDX library, and I found out after I made the game that I needed to work out different screen sizes. It was really hard to do so after the fact, and so I wanted to start with that here, so I don’t run into the same problem.

I found several guides on how one might accomplish this in Godot. Some just moved the camera further or closer based on the size of the screen. Some used the stretch mode. Others scaled objects to meet the criteria. And yet others had different assets to use for various sizes. So, after viewing all three, here is what I came up with:

First, I made a global.gd script that is automatically loaded as soon as the game starts. It looks like this:

extends Node

var my_viewport_scale = 1
var actualScreenRatio = 1.777777
var numberOfPlayers = 1
var computerPlayer = 0

func _ready():
randomize()
var viewport = get_node(“/root”).get_children()[1].get_viewport_rect().size
# comment the below call for OS.get_screen_size() if you want to force a
# certain screen size. If this call is not commented out, it will use the
# native screen size of the device or computer, unless it is greater than
# the Godot project settings of 1920*1080.
viewport = OS.get_screen_size()
# print(viewport) # Reference only

actualScreenRatio = viewport.x / viewport.y

my_viewport_scale = viewport.y/1080

So, in this script, I am asking the operating system for the size of the screen. I am building the entire game in a 1920×1080 resolution, but I use this math to find the screen ratio, and the screen size. I then set a scale based on the y axis ( my research seemed to point to that one being the best to base off of, but I don’t know particularly why ). Once these variables are in place, I can put this at the beginning of any “scene” that I make:

func _ready():
# My way to change the scale for every screen size. This allows me to build
# in 1920 x 1080 resolution, and scale it for bigger or smaller displays.
# This works for both 16:9 and 4:3 screen ratios, although 4:3 may not have
# the proper look.
# Tested satisfactory on: 800×480, 854×480, 800×600, 960×540, 1024×600, 1280×768, 1280×720, 1280×800, 1824×1200, 1920×1080
# Did not work well on 1400×900.

var new_y_scale = get_node(“/root/global”).my_viewport_scale
var new_x_scale = new_y_scale

if get_node(“/root/global”).actualScreenRatio < 1.4 :
new_x_scale = new_y_scale * ((800/600) / get_node(“/root/global”).actualScreenRatio)
elif get_node(“/root/global”).actualScreenRatio < 1.61 :
new_x_scale = new_y_scale / (get_node(“/root/global”).actualScreenRatio/1.45)
elif get_node(“/root/global”).actualScreenRatio < 1.76 :
new_x_scale = new_y_scale / (get_node(“/root/global”).actualScreenRatio/1.54)
else :
new_x_scale = new_y_scale

get_node(“/root”).get_children()[1].set_scale(Vector2(new_x_scale,new_y_scale))
# print(get_node(“/root”).get_children()[1].get_scale()) # Reference only.

In the title screen scene, I use the “ready” function, which is called as soon as a scene launches, to set the scale of that scene/node. By doing this, everything in that scene is not directly scaled, but is automatically set to scale as the whole view is scaled. This saves me from scaling each object, but rather scaling the view as a whole. At least, as far as I understand it in my limited knowledge of Godot and 3D programming, and it seems to work well.

The key part being that I want to check the actual screens ratio. Once I have that actual ratio from the global script, I can edit the scenes scale to match (at least closely) the ratio of the screen, and scale appropriately. This allows the game to be viewed on a 4:3 ratio screen (like an older computer), and a 16:9 screen (like most phones and modern computers), and several odd phone screen sizes.

As you can see in my notes, I tested this out on various screen sizes, and it worked well on a lot of them. This does have one downside, as it will make some screen resolutions look “squished” a tiny bit. But, with my limited repository of Godot/3D knowledge, this will do for now.

Here is an example from a 3D scene, the game table:

func _ready():
var new_y_scale = get_node(“/root/global”).my_viewport_scale
var new_x_scale = new_y_scale
var new_z_scale = new_y_scale

if get_node(“/root/global”).actualScreenRatio < 1.4 :
new_x_scale = new_y_scale * ((800/600) / get_node(“/root/global”).actualScreenRatio)
elif get_node(“/root/global”).actualScreenRatio < 1.61 :
new_x_scale = new_y_scale / (get_node(“/root/global”).actualScreenRatio/1.45)
elif get_node(“/root/global”).actualScreenRatio < 1.76 :
new_x_scale = new_y_scale / (get_node(“/root/global”).actualScreenRatio/1.54)
else :
new_x_scale = new_y_scale

get_node(“/root”).get_children()[1].set_scale(Vector3(new_x_scale,new_y_scale,new_z_scale))
# And set the HUD scale…
get_node(“ControlHUD”).set_scale(Vector2(new_x_scale,new_y_scale))
get_node(“winControl”).set_scale(Vector2(new_x_scale,new_y_scale))
# print(get_node(“/root”).get_children()[1].get_scale()) # Reference only.

Most looks the same, with the addition of scaling the “HUD” display. In the 3D scene, I over-layed a control node with essentially a 2D scene where the controls and such are floating above the game and within view for the player to use. Of course there is also another axis to change, the “z” axis, which is added in as well.

So, perhaps there is a better way to do this, but it seems to work pretty well for me. I also don’t notice any sort of performance hit doing this, as it only scales once at the scene change, and the scenes seem to load instantly. So I’ll stick with this for now. If you want to check out the project as a whole, you can find it on my GitLab.

Linux – keep it simple.

Is it “Go-Dot” or “Guh-Doe”?

Godot_part1

Since I decided to go with Godot, I’ve really, really been enjoying the experience. Of course it is a tremendous help to learn how to use it while making a game and following a great tutorial by BornCG on YouTube. This guy is a really great teacher! So if you are looking to learn how to use Godot, be sure to check his tutorials out! By the way, according to BornCG, it’s pronounced “Guh-Doe”.

godot_2

This is a slight detour from my intended project, but I like to have a successful path forward where I start using a new tool or programming language by using some sort of guide or tutorial. I find it helpful in making sure my environment is set up properly, especially if I get the same results as the instructor. So far, his tutorial is a pac-man/sonic like game where your sphere picks up coins and avoids enemies. I’ll try to include my final result when I get done!

So far, we have not used any native code, which you can program in yourself, using c++ or other languages. We’ve only been using GDScript thus far, which is an open source Python like language that is pretty simple to grasp. I look forward to seeing how we implement that, but either way, I’m certainly having fun!

Linux – keep it simple.