64M35 4-2: A Practice Passion Project

#1

64M35 4-2 (read: “Games for two”)
Captain’s log No.1, 2021-03-05T23:08:00Z:

If anyone is listening (or not), I’ve decided to make a couch co-op game to practice making a comprehensive game for Windows. Utilising simple controls (Directional keys, an action button and pause), particle effects, (OpenGL) materials, sound and a fun level selection system, I wish to create a fun game for two people on one keybord or two controllers, learning how to use Defold and, more importantly, make a game along the way.

The general idea is to make a lot of mini-games for two players accessible from a “central hub” level select menu. The mini-games would be simple and recognisable, such as Pong, a Wolfenstein 3D-esque level, tower defense, football etc.

I tackled the same idea three years ago, but I lost my drive after clashing with the physics trying to bounce the ball off the paddles in Pong.

I’ll see how much I’ll be able to do, studying at uni at the same time.
Feel free to follow me along the way, learn with me or drop suggestions.

End log, 2021-03-05T23:17:00Z

10 Likes

#2

Captain’s log No.2, 2021-03-05T23:23:00Z:
P0N6 AKA: Pong

I’ve been working on this for about three days before making a topic on this forum, so I’m logging my progress here.

T+3days:
I created this project.
I ran through the files, checking out what they had to offer and reacquainted myself with Defold and the different files available.
I set up inputs in input/game.input_binding for WASD, Arrow keys and Controller and edited the game.project file a bit.
I drew simple shapes and set up game object files for Pong.

T+2days:
I started work on the scripts. Programmed the paddles first making them actually move. Referencing APIs, Manuals and Examples from Defold Learn I slowly built my code, excessively studying it if it failed. Then the ball.

ball.script
function init(self)
       --[[excerp from the code]]--
    math.randomseed(os.time())
    math.random();math.random();math.random();    --clear consistent RNG buffer
    local dir = math.random(0, 360)	--set initial direction
    while dir == 90 or dir == 270 do
    	dir = math.random(0, 360)
    end
    dir = math.rad(dir)
    self.speed = 1000
    self.vel = vmath.vector3(self.speed * math.cos(dir), self.speed * math.sin(dir), 0)
end

This is what the code looks like now. It used to be a lot more messy, until I figured out velocity is a vector. I used to define the ball’s velocity with a speed and a directional angle. That means that my update(self, dt) function handled velocity calculating each movement separately using math.sin() and math.cos().
That worked until I had to program the ball “reflecting” (same angle in, same angle out) of the walls. See, me using the angle to move meant I had to make separate calculations for each of the four quadrants the ball was oriented towards, leaving me with clunky code. Now, well, it’s much simpler:

function reflect(self)	--linear relfection of ball on out of bounds
	self.vel.y = 0 - self.vel.y
end

After reflecting the ball off the walls (see details^) I added the ability for the ball to bounce off the paddles. Since I switched to a civilised vector math, it was easy. I dove into the on_message(self, message_id, message, sender) function and message_id = "contact_point_response".
I wanted to make the bounce directional based on where on the paddle the ball lands, instead of the boring constant angle, giving the game some strategy. So, I calculate the excentricity of the hit:

ball.script#bounce()
function on_message(self,message_id,message,sender)
    local pad = message.other_position
    local bal = message.position
    local where = bal.y - pad.y	--where on paddle:  excentricity of the hit to paddle's center
    local touch = where / 70	--ratio excentricity:half_paddle_size =~ excentricity [%]
    bounce(self,touch)
end
function bounce(self, touch)	--directional bounce off paddles; touch: where on the paddle the ball touches
    local dirX = 0
    if self.vel.x > 0 then	--direction left_right
        dirX = -1
    else
        dirX = 1
    end
    local dir = math.rad(75) * touch	--y rotational direction based on paddle hit location
    self.vel.x = math.cos(dir) * dirX * self.speed
    self.vel.y = math.sin(dir) * self.speed
end   

T+1day_yesterday:
I deleted the bit of code within ball.script that restricted the game object between y=50 and y=700 and replaced it with another game object that had a sprite and collision object. After I remembered to set it to “static” and after a long and painful process of trouble-shooting leading to me also remembering what masks on a collision object are for, I was set to start. And boy was it painful.
For some reason my reflect(self) code seemingly broke. I figured since the physics object sends out multiple events, I should make sure it reflects once, thus saving the last border under self.lstBrdr=message.other_id, since the ball can’t hit the same border twice. Nothing resolved the issue and all the code seemed fine.

Long story short, turns out the if not command works under very specific conditions in Lua that I hadn’t realised.

if not self.lstBrdr == message.other_id then  --> breaks everything
if not (self.lstBrdr == message.other_id) then -->works flawlessly

Yup… Two parenthesis made all the difference.

I also set up a simple score-keeping gui, that changes based on how many times the paddles have hit the ball and also changes colour based on which was last.
…once I figured out the msg.post(id, message_id, message) function, of course, or rather the addressing bit and how to write an id in the first place.

T-0_current:
This is the state of it right now:


You are now up-to-date

End Log, 2021-03-06T00:30:00Z

13 Likes

#3

Captain’s log No.3, 2021-03-07T13:37:00Z
Still P0N6

T+1day_yesterday
What I’d made thus far worked great. I found some bugs, which I’ll get into later, but it worked well.
Until it didn’t… When I added stuff.


score.gui_script
I added four more text nodes to score.gui and set up code that would serve me later. I added nodes that would display how often the player had won total, reading from a save_file and saving it back onto it each time that player won/when the level got unloaded - haven’t decided yet. The other two nodes would display the current victories in the on-going game. Added code to the goal.go object that would send an update to the gui script and later to reset.go#reset.script as well.

And that’s where it all broke

The gui#scoreTxt node didn’t update anymore at all. I tried trouble-shooting by shoving pprint()-s everywhere. All the necessary functions got called, which they should, because I hadn’t changed them and they worked before.
For whatever reason msg.post(hash("/gui"), hash("score_update"), update = {colour, score}), which worked flawlessly the day before, resulted in an empty message sent to the gui object. EMPTY! No clue why!
To add insult to injury, when I moved onto programming the current score nodes, the update intended for paddle bounces updated the winning text of player 2 specifically??

Messaging system
--[[ball.script]]--
local update = {colour = self.colours[self.index[self.lstHit]], score = self.score}
msg.post(hash("/gui"), hash("score_update"), update)
--[[Goal.script]]--
local message = {id = self.id, updt = 1}
msg.post(hash("/gui"), hash("current_win"), message)
--[[score.gui_script]]--
function on_message(self, message_id, message, sender)
	pprint("gui", message_id, message, sender)
	if message_id == hash("score_update") then
		gui.set_color(self.scoreTxt, message.colour)
		gui.set_text(self.scoreTxt, tostring(message.score))
	end
	if message_id == hash("current_win") then
		update_crrnt(self, message)
	end
end

I checked about a million times for typos and other errors, and I have exactly 0 clue why the update goes through to "current_win". The Goal.script update, by the way, also updates the current win node, as intended. I don’t know… I moved on, to change my environment and not get beat up by this.


reset.go
I added a reset game object with nothing but a reset.script inside. First I wanted to just reload the collection, but since I’m utilising current_win text nodes in the gui and reloading the entire collection would lose all data without a .temp file (and I didn’t want to bother with that too, just for this) I decided I’d reset each game objects position and re-initialise them.
I quickly learned Lua’s for loops and tables aren’t exactly friends so I had to work around that, and then I learned that Lua doesn’t have any try-catch structures either, and pcall() and xcall() don’t do a n y t h i n g in my case, so I had to work around that too.
In the end, the script doesn’t work. It doesn’t even reset positions of objects, one way or another, let alone re-initialise them.


bug_report

  • Two people can’t move up/down at once - I though that may’ve been an issue with my keyboard, but the issue arises as well when I plug in my controller. I have no idea how to tackle that and no wish to deal with it just yet.
  • If you squeeze the ball between the wall and paddle it easily glitches out - I should be able to fix that, but I’m not quite ready for hundreds of lines of code
  • You can glitch your player out of bounds if you hold up and down simultaneously Doesn’t work on the bottom border, just the top, - I guess Up trumps Down, but I need fix that.
  • the fact that nothing works - yeah…

End log, 2021-03-07T14:06:00Z

5 Likes

#4

I really appreciate that you share your project updates like this. It is great to follow along and see your learning experience. Thank you!

I suggest that you share your project as a zip file (exclude the .internal, .git and build folders). Me or someone else can take a look and help you move forward with your project.

2 Likes

#5

This should be it. I’m very puzzled why this doesn’t work as it should anymore.

Zipped project (657.9 KB)

I should say, my code might be messy and overcomplicated, but it’s well commented.
And since this is a learning process, some things may be built in strange ways and I wouldn’t even know.

But if anyone is willing to check this out, I’d be very grateful. :slight_smile:

0 Likes

#6

Ok, so one thing that stands out to me is the fact that you are using global functions in your scripts. Variables (and function definitions) in Lua are global by default. To make a function or variable local you put the local keyword before the declaration:

--
-- GLOBAL
--

foovar = "This is a global variable. It can be accessed from any script."

function foofun()
    print("This is a global function. It can be called from any script")
end

--
-- LOCAL
--
local foovar = "This is a local function. It is available in the current scope and after it has been declared"

local function foofun()
    print("This is a local function. It can be called from the current scope and after it has been declared")
end

The use of global functions becomes a problem in your project since you have multiple definitions of the score() function (once in Goal.script and once in ball.script). The result is that one of the function will be replaced by the other when it is declared. And when you call score() from Goal.script and ball.script unexpected things may happen since you are not calling the function you expect to be calling.

My recommendation: Go back and change all of the non-lifecycle function (init, update, on_message etc) to local functions and make sure you declare things in the right order.

If two functions need to call each other you can solve it like this:

local a = nil
local b = nil

a = function()
    b()
end

b = function()
    a()
end
4 Likes

#7

I was completely unaware global functions extended across the entire Lua runtime.

I made most of them local (a few might come in handy globally) and made sure they would be defined in time to be called.

One I just placed at the top after function init(self) the rest I called in advance with:

function init(self)
    local score
    local speedUp
end
***
function score(self)
    --blah blah blah
end
...

Everything I’d made until now works just the way I need it to.
Thank you so much! You saved me a few headaches.
: )

0 Likes

#8

EDIT: Nevermind, silly mistake, fixed it already :sweat_smile:

0 Likes