Great!
I mean the part you just added, with the trails, when you win.
Great!
I mean the part you just added, with the trails, when you win.
At the end cards fall down one stack at a time, maybe make it all four altogether? Should look fancier.
When card is picked, make it bigger (scale up) and add a shadow underneath. That will make an illusion of a raised card above the table. The same technique can be used when a card is flipped, and since Defold is 3D, make a proper 3D rotation with perspective (when one side gets bigger during the rotation).
Add a texture background that looks more like a textile and some slight dark gradient to make a feel of a lamp nearby.
Cards transition linearly? Some easing could also make it look snappier.
No lose state determination?
Many of this things already in to-do list.
We have no plans for monetization of this game - we don’t want to spend many time for it. Time to time I will make some features and improvements, only when I have no any other things.
Thank you for your feedback and suggestions.
One more programmer joined to our team, which means that we will be able to do a little more =)
Our solitaire was updated to version 1.55
What’s new:
Android and HTML5 version already updated. IOS build - Waiting for Review.
How much did you manage to reduce the size, and how?
The build size reduced using buildmanifest:
# Release + No: Physics + Record + Profiler + Facebook
platforms:
js-web:
context:
excludeLibs: ["BulletDynamics", "BulletCollision", "LinearMath", "Box2D", "physics", "record", "vpx", "engine", "profilerext"]
excludeSymbols: ["FacebookExt", "ProfilerExt"]
libs: ["engine_release", "physics_null", "record_null"]
armv7-android:
context:
excludeLibs: ["BulletDynamics", "BulletCollision", "LinearMath", "Box2D", "physics", "record", "vpx", "engine", "profilerext"]
excludeSymbols: ["FacebookExt", "ProfilerExt"]
libs: ["engine_release", "physics_null", "record_null"]
armv7-ios:
context:
excludeLibs: ["BulletDynamics", "BulletCollision", "LinearMath", "Box2D", "physics", "record", "vpx", "engine", "profilerext"]
excludeSymbols: ["FacebookExt", "ProfilerExt"]
libs: ["engine_release", "physics_null", "record_null"]
arm64-ios:
context:
excludeLibs: ["BulletDynamics", "BulletCollision", "LinearMath", "Box2D", "physics", "record", "vpx", "engine", "profilerext"]
excludeSymbols: ["FacebookExt", "ProfilerExt"]
libs: ["engine_release", "physics_null", "record_null"]
iOS:
Android:
OLD Version: 3.06 Mb
NEW Version: 2.58 Mb
Html5 gzip:
OLD Version :
Wow, fantastic!
I just received few crash reports, maybe you can help, pls?
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Huawei/ALE-L23/hwALE-H:5.0.1/HuaweiALE-L23/C605B150:user/release-keys'
Revision: '0'
ABI: 'arm'
pid: 32342, tid: 32433, name: Thread-303 >>> com.potatojam.classic.solitaire.klondike <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: '../src/opengl/graphics_opengl.cpp:1646: void dmGraphics::SetTexture(dmGraphics::HTexture, const dmGraphics::TextureParams&): assertion "params.m_Height <= g_Context->m_MaxTextureSize" failed'
r0 00000000 r1 00007eb1 r2 00000006 r3 00000000
r4 e302bdd8 r5 00000006 r6 00000002 r7 0000010c
r8 e302b764 r9 e302b740 sl e3291adc fp ab545438
ip 00007eb1 sp e302b5e8 lr f707d699 pc f70a3a94 cpsr 600f0010
backtrace:
#00 pc 0003ca94 /system/lib/libc.so (tgkill+12)
#01 pc 00016695 /system/lib/libc.so (pthread_kill+52)
#02 pc 000172a7 /system/lib/libc.so (raise+10)
#03 pc 00013be5 /system/lib/libc.so (__libc_android_abort+36)
#04 pc 000123bc /system/lib/libc.so (abort+4)
#05 pc 00014dc3 /system/lib/libc.so (__libc_fatal+16)
#06 pc 00013c69 /system/lib/libc.so (__assert2+20)
#07 pc 000d795c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmGraphics::SetTexture(dmGraphics::Texture*, dmGraphics::TextureParams const&)+496)
#08 pc 000d900c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmGraphics::NewRenderTarget(dmGraphics::Context*, unsigned int, dmGraphics::TextureCreationParams const*, dmGraphics::TextureParams const*)+612)
#09 pc 000e683c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmRender::RenderScript_RenderTarget(lua_State*)+1512)
#10 pc 0010f4f8 /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so
Tombstone written to: /data/tombstones/tombstone_00
And second:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/j1acevelteub/j1acevelte:5.1.1/LMY47V/J111MUBU0AQF2:user/release-keys'
Revision: '4'
ABI: 'arm'
pid: 21771, tid: 21853, name: Thread-1856 >>> com.potatojam.classic.solitaire.klondike <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: '../src/opengl/graphics_opengl.cpp:1646: void dmGraphics::SetTexture(dmGraphics::HTexture, const dmGraphics::TextureParams&): assertion "params.m_Height <= g_Context->m_MaxTextureSize" failed'
r0 00000000 r1 0000555d r2 00000006 r3 00000000
r4 a2dcfdb8 r5 00000006 r6 00000009 r7 0000010c
r8 a2dcf744 r9 a2dcf720 sl a302eadc fp b4772e80
ip 0000555d sp a2dcf5c8 lr b6de3735 pc b6e06dbc cpsr 600f0010
backtrace:
#00 pc 0003adbc /system/lib/libc.so (tgkill+12)
#01 pc 00017731 /system/lib/libc.so (pthread_kill+52)
#02 pc 00018347 /system/lib/libc.so (raise+10)
#03 pc 00014be1 /system/lib/libc.so (__libc_android_abort+36)
#04 pc 00012f70 /system/lib/libc.so (abort+4)
#05 pc 00015e25 /system/lib/libc.so (__libc_fatal+16)
#06 pc 00014c65 /system/lib/libc.so (__assert2+20)
#07 pc 000d795c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN10dmGraphics10SetTextureEPNS_7TextureERKNS_13TextureParamsE+496)
#08 pc 000d900c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN10dmGraphics15NewRenderTargetEPNS_7ContextEjPKNS_21TextureCreationParamsEPKNS_13TextureParamsE+612)
#09 pc 000e683c /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN8dmRender25RenderScript_RenderTargetEP9lua_State+1512)
#10 pc 0010f4f8 /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so
Tombstone written to: /data/tombstones/tombstone_01
[ 11-26 01:49:01.121 183: 183 E/ ]
ro.product_ship = true
[ 11-26 01:49:01.121 183: 183 E/ ]
ro.debug_level = 0x4f4c
Strange, but some error with m_MaxTextureSize
We have only one texture in game:
1024x256
and second one I make dynamically in render script:
local helper = require "modules.render.helper"
local msgs = require "modules.msgs"
local RT_halfsize = 50
local function update_render_targets(self, initial)
if not initial then
render.delete_render_target(self.render_target)
end
self.RT_color_params.width, self.RT_depth_params.width = helper.win_width, helper.win_width
self.RT_color_params.height, self.RT_depth_params.height = helper.win_height, helper.win_height
self.render_target = render.render_target("target", {[render.BUFFER_COLOR_BIT] = self.RT_color_params,
[render.BUFFER_DEPTH_BIT] = self.RT_depth_params})
local x = RT_halfsize * 2 / helper.win_width
local y = RT_halfsize * 2 / helper.win_height
self.RT_proj = vmath.matrix4_orthographic(-RT_halfsize + x, RT_halfsize - x,
- RT_halfsize + y, RT_halfsize - y, - 1, 1)
self.firstFrame = true
end
local function update_window(self)
helper.win_width = render.get_window_width()
helper.win_height = render.get_window_height()
if helper.win_width >= helper.win_height then
helper.settings_width = render.get_height()
helper.settings_height = render.get_width()
else
helper.settings_width = render.get_width()
helper.settings_height = render.get_height()
end
helper.zoom_factor = math.min(helper.win_width / helper.settings_width, helper.win_height / helper.settings_height)
self.projected_width = helper.win_width / helper.zoom_factor
self.projected_height = helper.win_height / helper.zoom_factor
helper.xoffset = - (self.projected_width - helper.settings_width) / 2
helper.yoffset = - (self.projected_height - helper.settings_height) / 2
helper.adjust_stretch_x = helper.win_width / helper.settings_width
helper.adjust_stretch_y = helper.win_height / helper.settings_height
helper.adjust_zoom = math.max(helper.adjust_stretch_x, helper.adjust_stretch_y)
helper.adjust_fit = math.min(helper.adjust_stretch_x, helper.adjust_stretch_y)
self.full_proj = vmath.matrix4_orthographic(helper.xoffset, helper.xoffset + self.projected_width,
helper.yoffset, helper.yoffset + self.projected_height, - 1, 1)
self.view = vmath.matrix4()
self.gui_projection = vmath.matrix4_orthographic(0, helper.win_width, 0, helper.win_height, - 1, 1)
end
function init(self)
self.tile_pred = render.predicate({"tile"})
self.gui_pred = render.predicate({"gui"})
self.text_pred = render.predicate({"text"})
self.render_target_pred = render.predicate({"render_target"})
helper.settings_width = render.get_width()
helper.settings_height = render.get_height()
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.RT_color_params = {
format = render.FORMAT_RGBA,
width = render.get_window_width(),
height = render.get_window_height(),
min_filter = render.FILTER_LINEAR,
mag_filter = render.FILTER_LINEAR,
u_wrap = render.WRAP_CLAMP_TO_EDGE,
v_wrap = render.WRAP_CLAMP_TO_EDGE
}
self.RT_depth_params = {
format = render.FORMAT_DEPTH,
width = render.get_window_width(),
height = render.get_window_height(),
u_wrap = render.WRAP_CLAMP_TO_EDGE,
v_wrap = render.WRAP_CLAMP_TO_EDGE
}
self.clear_param = {
[render.BUFFER_COLOR_BIT] = self.clear_color,
[render.BUFFER_DEPTH_BIT] = 1,
[render.BUFFER_STENCIL_BIT] = 0
}
self.view = vmath.matrix4()
update_window(self)
update_render_targets(self, true)
end
function update(self)
render.set_depth_mask(true)
render.clear(self.clear_param)
render.set_viewport(0, 0, helper.win_width, helper.win_height)
render.set_view(self.view)
render.set_depth_mask(false)
render.disable_state(render.STATE_DEPTH_TEST)
render.disable_state(render.STATE_STENCIL_TEST)
render.enable_state(render.STATE_BLEND)
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
render.disable_state(render.STATE_CULL_FACE)
render.set_projection(self.full_proj)
if self.is_render_target then
render.enable_render_target(self.render_target)
if self.firstFrame then
self.firstFrame = false
render.clear(self.clear_param)
end
render.draw(self.tile_pred)
render.disable_render_target(self.render_target)
render.set_view(self.view)
render.set_projection(self.RT_proj)
render.enable_texture(0, self.render_target, render.BUFFER_COLOR_BIT)
render.draw(self.render_target_pred)
render.disable_texture(0, self.render_target)
else
render.draw(self.tile_pred)
end
render.set_view(self.view)
render.set_projection(self.gui_projection)
render.enable_state(render.STATE_STENCIL_TEST)
render.draw(self.gui_pred)
render.draw(self.text_pred)
render.disable_state(render.STATE_STENCIL_TEST)
render.set_depth_mask(false)
end
function on_message(self, message_id, message)
if message_id == hash("clear_color") then
self.clear_color = message.color
self.clear_param[render.BUFFER_COLOR_BIT] = self.clear_color
elseif message_id == hash("set_view_projection") then
self.view = message.view
elseif message_id == msgs.WINDOW_RESIZED then
update_window(self)
update_render_targets(self)
msg.post("main:/main#main", message_id, message)
elseif message_id == msgs.R_TARGET then
self.is_render_target = message.isOn
if self.is_render_target then
update_render_targets(self)
end
end
end
The assert you are seeing is a check so that we don’t create a render target with dimensions greater than what is supported by the device
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
...
assert(params.m_Height <= max_texture_size);
Maybe helper.win_width
for some reason get assigned too big a value?
I have only one place where I set this value (in render script):
helper.win_width = render.get_window_width()
helper.win_height = render.get_window_height()
Both device has smaller screen size than supported texture.
Is it possible that Init of render_sript runs earliar that initialization of global render module and I recieve some default value in render.get_window_width() instead of real size?
(I have no this device to check this value - logs from google console)
There are also some glGetError
checks when creating textures in dmGraphics::SetTexture
which could unfortunately result in assert
if they fail. However I can’t see any issues with the parameters you pass it…
But it might be a good idea to do these to things (based on my own experience) anyway:
CLAMP_TO_EDGE
should be able to handle NPOT textures).NEAREST
filtering unless you really need LINEAR
(I guess you use the render target as a post-process so it’s always 1:1 from texture to screen fragments, so linear doesn’t really do much here anyway).Cool, thanks.
I also find this:
https://aras-p.info/blog/2012/10/17/non-power-of-two-textures/
ES 2.0 has limited NPOT support (no mipmaps, no Repeat wrap mode) in core; and ES 1.1 has no NPOT support.
I try to set and retest build with next options for material:
Does somebody have somebody device with Mali-400MP2 or Mali-T830MP2 ?
UPD: fixed problem on P8 Lite but not with Galaxy J1 Ace =(
UPD2: : the following render script with POT render target help me with my issue. Thank you @sven
local helper = require "modules.render.helper"
local msgs = require "modules.msgs"
local RT_halfsize = 50
local function next_pot(num)
local result = 1
while num > result do
result = bit.lshift(result, 1)
end
return result
end
local function update_render_targets(self, initial)
if not initial then
render.delete_render_target(self.render_target)
end
local pot_width = next_pot(helper.win_width)
local pot_height = next_pot(helper.win_height)
self.RT_color_params.width, self.RT_depth_params.width = pot_width, pot_width
self.RT_color_params.height, self.RT_depth_params.height = pot_height, pot_height
self.render_target = render.render_target("target", {[render.BUFFER_COLOR_BIT] = self.RT_color_params,
[render.BUFFER_DEPTH_BIT] = self.RT_depth_params})
local RT_SIZE = RT_halfsize * 2
local x = RT_SIZE / pot_width * (pot_width - helper.win_width)
local y = RT_SIZE / pot_height * (pot_height - helper.win_height)
local nx = RT_SIZE / helper.win_width
local ny = RT_SIZE / helper.win_height
self.RT_proj = vmath.matrix4_orthographic(-RT_halfsize + nx, RT_halfsize - x - nx,
- RT_halfsize + ny, RT_halfsize - y - ny, - 1, 1)
self.firstFrame = true
end
local function update_window(self)
helper.win_width = render.get_window_width()
helper.win_height = render.get_window_height()
if helper.win_width >= helper.win_height then
helper.settings_width = render.get_height()
helper.settings_height = render.get_width()
else
helper.settings_width = render.get_width()
helper.settings_height = render.get_height()
end
helper.zoom_factor = math.min(helper.win_width / helper.settings_width, helper.win_height / helper.settings_height)
self.projected_width = helper.win_width / helper.zoom_factor
self.projected_height = helper.win_height / helper.zoom_factor
helper.xoffset = - (self.projected_width - helper.settings_width) / 2
helper.yoffset = - (self.projected_height - helper.settings_height) / 2
helper.adjust_stretch_x = helper.win_width / helper.settings_width
helper.adjust_stretch_y = helper.win_height / helper.settings_height
helper.adjust_zoom = math.max(helper.adjust_stretch_x, helper.adjust_stretch_y)
helper.adjust_fit = math.min(helper.adjust_stretch_x, helper.adjust_stretch_y)
self.full_proj = vmath.matrix4_orthographic(helper.xoffset, helper.xoffset + self.projected_width,
helper.yoffset, helper.yoffset + self.projected_height, - 1, 1)
self.view = vmath.matrix4()
self.gui_projection = vmath.matrix4_orthographic(0, helper.win_width, 0, helper.win_height, - 1, 1)
end
function init(self)
self.tile_pred = render.predicate({"tile"})
self.gui_pred = render.predicate({"gui"})
self.text_pred = render.predicate({"text"})
self.render_target_pred = render.predicate({"render_target"})
helper.settings_width = render.get_width()
helper.settings_height = render.get_height()
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.RT_color_params = {
format = render.FORMAT_RGBA,
width = render.get_window_width(),
height = render.get_window_height(),
min_filter = render.FILTER_LINEAR,
mag_filter = render.FILTER_LINEAR,
u_wrap = render.WRAP_CLAMP_TO_EDGE,
v_wrap = render.WRAP_CLAMP_TO_EDGE
}
self.RT_depth_params = {
format = render.FORMAT_DEPTH,
width = render.get_window_width(),
height = render.get_window_height(),
u_wrap = render.WRAP_CLAMP_TO_EDGE,
v_wrap = render.WRAP_CLAMP_TO_EDGE
}
self.clear_param = {
[render.BUFFER_COLOR_BIT] = self.clear_color,
[render.BUFFER_DEPTH_BIT] = 1,
[render.BUFFER_STENCIL_BIT] = 0
}
self.view = vmath.matrix4()
update_window(self)
update_render_targets(self, true)
end
function update(self)
render.set_depth_mask(true)
render.clear(self.clear_param)
render.set_viewport(0, 0, helper.win_width, helper.win_height)
render.set_view(self.view)
render.set_depth_mask(false)
render.disable_state(render.STATE_DEPTH_TEST)
render.disable_state(render.STATE_STENCIL_TEST)
render.enable_state(render.STATE_BLEND)
render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
render.disable_state(render.STATE_CULL_FACE)
render.set_projection(self.full_proj)
if self.is_render_target then
render.enable_render_target(self.render_target)
if self.firstFrame then
self.firstFrame = false
render.clear(self.clear_param)
end
render.draw(self.tile_pred)
render.disable_render_target(self.render_target)
render.set_view(self.view)
render.set_projection(self.RT_proj)
render.enable_texture(0, self.render_target, render.BUFFER_COLOR_BIT)
render.draw(self.render_target_pred)
render.disable_texture(0, self.render_target)
else
render.draw(self.tile_pred)
end
render.set_view(self.view)
render.set_projection(self.gui_projection)
render.enable_state(render.STATE_STENCIL_TEST)
render.draw(self.gui_pred)
render.draw(self.text_pred)
render.disable_state(render.STATE_STENCIL_TEST)
render.set_depth_mask(false)
end
function on_message(self, message_id, message)
if message_id == hash("clear_color") then
self.clear_color = message.color
self.clear_param[render.BUFFER_COLOR_BIT] = self.clear_color
elseif message_id == hash("set_view_projection") then
self.view = message.view
elseif message_id == msgs.WINDOW_RESIZED then
update_window(self)
update_render_targets(self)
msg.post("main:/main#main", message_id, message)
elseif message_id == msgs.R_TARGET then
self.is_render_target = message.isOn
if self.is_render_target then
update_render_targets(self)
end
end
end
We made update 1.60:
How are player metrics doing?
Pretty good.
Audience is growing up stable, slow but stable (Daily app installs are bigger than daily app removes)
Retention (ios only)
Day 2: 20 - 25%
Day 7: 10-20%
Day 30: 5-10%
Now we are working on the new update with one major feature - undo, and few small improvements.
I think it will be last major update. After that only minor updates with bug fixes and so on.
Maybe in future we itegrate google play services like leaderboards, achievements etc. But for now nobody makes this NE and we have other priorities too.
UPD:
Defold analytics didn’t work for android, maybe because I removed a lot of permissions.
Retention percent actual only for IOS, we have no data for android. I’ll try to collect this data after next update.
Are you tracking any other data? https://www.defold.com/community/projects/86424/ Might be helpful for that
I’d be curious to know what percentage of users play on landscape vs portrait layout for example. Did you have any trouble implementing that?
Are the artifacts in the second button due to Defold’s texture compression?
No, we didn’t integrate any analytics to this project.
I’ll think about it, but I’m not sure that we have time. We do not need analytics in this project.
No, this artefacts in original image (We will fix that when finished with major features and start to distribute HTML5 version.):
Have you published postmortems for any of your finished projects before?
No. I have a task in my personal task manager to make postmortem for Bring me cakes, but I have no time, and we still working on the game
We made update 1.71:
We will back to the project in future for adding of Google Play Services and Game Center but for now project is frozen.
We have plans to use this solitaire “engine” for make more original game in future.