Collapsing Worlds puzzle

I had this prototype for a while now and just polished it to a “good enough” shape. Not without some adventures.

I started learning Defold from the 3D point of view right from the beginning, I got a little tired of 2D, I wanted something fresh. I remember how I couldn’t figure out why my 3D models are not displayed, then I discovered the render script. And it amazed me.

Ease of shaders use and the render script are some of the best features of Defold. Along with being cross platform.

I used MagicaVoxel to draw the objects, they turn out to be so cute in that style.

Rotating 3D objects wasn’t intuitive, the euler properties just don’t work, so I had to use quaternions and multiply them in the update() function instead of go.animate().

I’ve implemented isometric camera, polar camera coordinates and rotation, point light, vignette, background gradient with dithering, color grading and depth of field postprocessing, ray casting from the camera to pick 3D objects, camera shake on collapsing. Added music, sounds and a game over screen to make it look like a real game. It was fun.

However it wasn’t fun when a few hours before the end of submission I decided to update Defold Editor 2 and no longer could build the game because of a bug (_GameroomExt symbol not found). Thankfully, Editor 1 was working fine, that’s strange however, they both should be building according to the same routines.

Then for some reason color grading shader got broken in Editor 1, LUT texture was clearly wrong, didn’t have time to figure what’s wrong out, so I just disabled it.

I’ve submitted Windows and macOS versions, but HTML5 version didn’t work. Turns out it doesn’t like when both vertex and fragment shaders have the same uniform variable. And it can’t work with postprocessing routine, during which a texture with a size of the screen is created and if the screen is not power of the two size, it fails in the browser. So I had to set my game resolution to 1024x1024 instead of original 640x960. And finally was able to submit an HTML5 version, but Chrome surprisingly can’t handle it performance wise, while both FireFox and Safari run it perfectly, so strange.

Then 2 minutes before the end of submission I realize I forgot to enable the music and rushed to the computer to make a new build with sounds enabled. Somehow I managed to upload on the last minute. Good speed of building, heh.

Thanks for organizing the jam!

View/play/rate here https://itch.io/jam/coronadefoldjam/rate/180091

9 Likes

Really cool! Have you tested it on iOS or Android? Did you mean to publish debug builds? You should keep updating on another non-jam page!

1 Like

Thanks! I have tested on Android, my render script is having troubles with adjusting to the screen size, will figure that out later.
No, debug builds are accidental, do they make any discomfort?

Eventually I will finish the game and publish it for Android/iOS and Steam.

Post your render script?

I’m also interested in getting 3d working well with Defold and have work going with DefFX but it’s not polished or usable yet and I won’t have time to make it ready for a while atm. Would you be interested in contributing some of your work to it? https://github.com/subsoap/deffx

Debug builds take more resources to run. It may in part explain why it’s so slow on Chrome.

I would change the click sound to be something short and soft (too long and noisy especially when there are multiple copies of it playing at once), reduce the amount of the screen shake, and increase the speed of collapsing - maybe make it exponential. I’m not sure the ray casting to pick 3D objects from camera is currently accurate.

Here is a bug

2 Likes

Current render script.

local camera = require('libs.camera')
local hashed = require('libs.hashed')

function init(self)
	camera.width = render.get_window_width()
    camera.height = render.get_window_height()
    
    self.gui_pred = render.predicate({'gui'})
    self.model_pred = render.predicate({'model'})
    self.background_pred = render.predicate({'background'})
    self.postprocessing_pred = render.predicate({'postprocessing'})

    self.clear_color = vmath.vector4(0, 0, 0, 0)
    self.clear_color.x = sys.get_config('render.clear_color_red', 0)
    self.clear_color.y = sys.get_config('render.clear_color_green', 0)
    self.clear_color.z = sys.get_config('render.clear_color_blue', 0)
    self.clear_color.w = sys.get_config('render.clear_color_alpha', 0)

    self.identity_matrix = vmath.matrix4()

    local target_params = {
		[render.BUFFER_COLOR_BIT] = {
			format = render.FORMAT_RGBA,
			width = render.get_width(),
			height = render.get_height()
		},
		[render.BUFFER_DEPTH_BIT] = {
			format = render.FORMAT_DEPTH,
			width = render.get_width(),
			height = render.get_height()
		}
	}

    self.original_target = render.render_target('original', target_params)
end

function update(self)
	-- Create an orbit camera.
    local camera_boom = vmath.vector3(0, 0, camera.distance)
    local camera_rotation = vmath.quat_rotation_y(-camera.alpha - camera.alpha_offset) * vmath.quat_rotation_x(camera.beta + camera.beta_offset)
    local camera_position = camera.target + vmath.rotate(camera_rotation, camera_boom)
    local camera_up = vmath.rotate(camera_rotation, vmath.vector3(0,1,0))
    local view_mtx = vmath.matrix4_look_at(camera_position, camera.target, camera_up)
    local ortho_w = camera.view_width
    local ortho_h = ortho_w * render.get_window_height() / render.get_window_width()
    local projection_mtx = vmath.matrix4_orthographic(-ortho_w, ortho_w, -ortho_h, ortho_h, 1, 1000)
    camera.view = view_mtx
    camera.projection = projection_mtx
    camera.width = render.get_window_width()
    camera.height = render.get_window_height()
    
	render.enable_render_target(self.original_target)
		render.enable_state(render.STATE_DEPTH_TEST)
		render.set_depth_mask(true)
		render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color, [render.BUFFER_DEPTH_BIT] = 1, [render.BUFFER_STENCIL_BIT] = 0})
		render.set_viewport(0, 0, render.get_window_width(), render.get_window_height())
	
		-- Render 2D space.
		render.set_view(self.identity_matrix)
		render.set_projection(vmath.matrix4_orthographic(0, render.get_window_width(), 0, render.get_window_height(), -1, 1))
		render.draw(self.background_pred)
	
		render.set_view(view_mtx)
		render.set_projection(projection_mtx)
	
		-- Render 3D space.
		render.enable_state(render.STATE_CULL_FACE)
		render.draw(self.model_pred)
		render.disable_state(render.STATE_CULL_FACE)
		render.disable_state(render.STATE_DEPTH_TEST)
		render.set_depth_mask(false)
	
		-- Render GUI and overlays.
		render.set_view(vmath.matrix4())
		render.set_projection(vmath.matrix4_orthographic(0, render.get_window_width(), 0, render.get_window_height(), -1, 1))
	
		render.enable_state(render.STATE_BLEND)
		render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
		render.draw(self.gui_pred)
		render.disable_state(render.STATE_BLEND)
    render.disable_render_target(self.original_target)
    
    -- Postprocessing.
    render.clear({[render.BUFFER_COLOR_BIT] = self.clear_color})

	render.set_viewport(0, 0, render.get_window_width(), render.get_window_height())
	render.set_view(self.identity_matrix)
	render.set_projection(self.identity_matrix)

	render.enable_texture(0, self.original_target, render.BUFFER_COLOR_BIT)
	render.draw(self.postprocessing_pred)
	render.disable_texture(0, self.original_target)
end

function on_message(self, message_id, message)
    if message_id == hashed.clear_color then
        self.clear_color = message.color
    end
end

DefFX helped me a couple times to understand Defold’s rendering, thanks for that project, it’s amazing. I’d like to contribute, but I don’t know how to do it best, I find that regarding 3D, it becomes so much more depended on the project’s initial ground base and developer’s perspective. Have you seen my Tank vs Meteors demo on GitHub? It has pretty much the same stuff on 3D.

Game definitely needs further adjustments and play testings, thanks for the notes. I want to make sound duration and shake duration depend on the number of tiles being collapsed or moved (will be another sound on an impact).

That’s not a bug at the moment, I just didn’t came up with a good solution what to do in that situation, but it’s totally predictable and at the moment it just makes the game a bit harder and more interesting to think about.

Ray casting should be accurate, it’s just set to pick the ground level of a tile, so that you can still click on a tile if it’s obstructed by a house’s roof for instance. At the same time if you try to click on the house on it’s roof, then yeah, it will not do that. In the future I will simply highlight the picked tile and adjacent tiles, that will solve a few problems.

1 Like

Issue with the ray casting is just that I had the window slightly scaled and it offset the position as there’s no compensation yet.

Community will eventually need a basic 3d starter project with fixed aspect ratio style window scaling support for the camera viewport / game window size. Have the kinds of features you implemented available in such an example project is good too as it makes it easier to adapt for other project needs. For DefFX for me part of long term goal is to include many such starter projects with examples of different mechanics used in 3d games.

Have you dug into at all to see why it runs slow in Chrome? My own 3D projects run fast.

Tested in Brave browser and it runs slow in it too.

Maybe disable features until something makes it run fast.

https://chrome.google.com/webstore/detail/spectorjs/denbgaamihkadbghdceggmchnflmhpmk?hl=en Might help.

I guess that’s due to limited support of asm.js/webassembly in Chrome.

Low performance in Chrome may be caused by not working release mode. It doesn’t work with some extensions. The simpliest way (as I know) to check if release mode works or not is to build your game with enabled web debuger. If you’ll see debug info in the browser it means that release mode doesn’t work.

1 Like

Hey @sergey.lerg, can you explain how you got textured models from MagicaVoxel into Defold?

MagicaVoxel -> export as OBJ -> import into Blender -> export as DAE -> import into Defold, use pallet file from MagicaVoxel as a texture for the models in Defold.

4 Likes

Aha!!! Bingo. Well that was easy. Somehow I wasted an hour or two trying to figure that out. Thanks a lot!

3 Likes