From 2b09ee726abdfb0daea060ba907e718bc8b83753 Mon Sep 17 00:00:00 2001 From: turanszkij Date: Thu, 18 Apr 2019 19:37:13 +0100 Subject: [PATCH] fixes + fighting game script update --- Editor/ForceFieldWindow.cpp | 4 +- Editor/LightWindow.cpp | 4 +- WickedEngine/wiRenderer.cpp | 28 +- WickedEngine/wiSceneSystem.cpp | 12 +- WickedEngine/wiSceneSystem.h | 9 +- WickedEngine/wiSceneSystem_BindLua.cpp | 6 + WickedEngine/wiSceneSystem_BindLua.h | 1 + models/Havoc/havoc.wiscene | Bin 5242167 -> 5241963 bytes models/dojo.wiscene | Bin 0 -> 1440 bytes scripts/fighting_game.lua | 875 ++++++++++++++++--------- 10 files changed, 587 insertions(+), 352 deletions(-) create mode 100644 models/dojo.wiscene diff --git a/Editor/ForceFieldWindow.cpp b/Editor/ForceFieldWindow.cpp index f52973fa9..73cc8072a 100644 --- a/Editor/ForceFieldWindow.cpp +++ b/Editor/ForceFieldWindow.cpp @@ -72,7 +72,7 @@ ForceFieldWindow::ForceFieldWindow(wiGUI* gui) : GUI(gui) ForceFieldComponent* force = wiSceneSystem::GetScene().forces.GetComponent(entity); if (force != nullptr) { - force->range = args.fValue; + force->range_local = args.fValue; } }); rangeSlider->SetEnabled(false); @@ -119,7 +119,7 @@ void ForceFieldWindow::SetEntity(Entity entity) { typeComboBox->SetSelected(force->type == ENTITY_TYPE_FORCEFIELD_POINT ? 0 : 1); gravitySlider->SetValue(force->gravity); - rangeSlider->SetValue(force->range); + rangeSlider->SetValue(force->range_local); forceFieldWindow->SetEnabled(true); } diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index 4946dd044..97ae5471f 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -42,7 +42,7 @@ LightWindow::LightWindow(wiGUI* gui) : GUI(gui) LightComponent* light = wiSceneSystem::GetScene().lights.GetComponent(entity); if (light != nullptr) { - light->range = args.fValue; + light->range_local = args.fValue; } }); rangeSlider->SetEnabled(false); @@ -246,7 +246,7 @@ void LightWindow::SetEntity(Entity entity) //lightWindow->SetEnabled(true); energySlider->SetEnabled(true); energySlider->SetValue(light->energy); - rangeSlider->SetValue(light->range); + rangeSlider->SetValue(light->range_local); radiusSlider->SetValue(light->radius); widthSlider->SetValue(light->width); heightSlider->SetValue(light->height); diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index fc4b1de73..ba468553a 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1235,7 +1235,7 @@ struct SHCAM void CreateSpotLightShadowCam(const LightComponent& light, SHCAM& shcam) { const float zNearP = 0.1f; - const float zFarP = max(1.0f, light.range); + const float zFarP = max(1.0f, light.GetRange()); shcam = SHCAM(XMFLOAT4(0, 0, 0, 1), zNearP, zFarP, light.fov); shcam.Update(XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation)) * XMMatrixTranslationFromVector(XMLoadFloat3(&light.position))); @@ -3967,7 +3967,7 @@ void UpdateRenderData(GRAPHICSTHREAD threadID) entityArray[entityCounter].SetType(light.GetType()); entityArray[entityCounter].positionWS = light.position; XMStoreFloat3(&entityArray[entityCounter].positionVS, XMVector3TransformCoord(XMLoadFloat3(&entityArray[entityCounter].positionWS), viewMatrix)); - entityArray[entityCounter].range = light.range; + entityArray[entityCounter].range = light.GetRange(); entityArray[entityCounter].color = wiMath::CompressColor(light.color); entityArray[entityCounter].energy = light.energy; entityArray[entityCounter].shadowBias = light.shadowBias; @@ -4055,8 +4055,8 @@ void UpdateRenderData(GRAPHICSTHREAD threadID) entityArray[entityCounter].SetType(force.type); entityArray[entityCounter].positionWS = force.position; entityArray[entityCounter].energy = force.gravity; - entityArray[entityCounter].range = 1.0f / max(0.0001f, force.range); // avoid division in shader - entityArray[entityCounter].coneAngleCos = force.range; // this will be the real range in the less common shaders... + entityArray[entityCounter].range = 1.0f / max(0.0001f, force.GetRange()); // avoid division in shader + entityArray[entityCounter].coneAngleCos = force.GetRange(); // this will be the real range in the less common shaders... // The default planar force field is facing upwards, and thus the pull direction is downwards: entityArray[entityCounter].directionWS = force.direction; @@ -4531,7 +4531,7 @@ void DrawLights(const CameraComponent& camera, GRAPHICSTHREAD threadID) { MiscCB miscCb; miscCb.g_xColor.x = float(entityArrayOffset_Lights + i); - float sca = light.range + 1; + float sca = light.GetRange() + 1; XMStoreFloat4x4(&miscCb.g_xTransform, XMMatrixTranspose(XMMatrixScaling(sca, sca, sca)*XMMatrixTranslationFromVector(XMLoadFloat3(&light.position)) * camera.GetViewProjection())); device->UpdateBuffer(&constantBuffers[CBTYPE_MISC], &miscCb, threadID); device->BindConstantBuffer(VS, &constantBuffers[CBTYPE_MISC], CB_GETBINDSLOT(MiscCB), threadID); @@ -4546,7 +4546,7 @@ void DrawLights(const CameraComponent& camera, GRAPHICSTHREAD threadID) miscCb.g_xColor.x = float(entityArrayOffset_Lights + i); const float coneS = (const float)(light.fov / XM_PIDIV4); XMStoreFloat4x4(&miscCb.g_xTransform, XMMatrixTranspose( - XMMatrixScaling(coneS*light.range, light.range, coneS*light.range)* + XMMatrixScaling(coneS*light.GetRange(), light.GetRange(), coneS*light.GetRange())* XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation))* XMMatrixTranslationFromVector(XMLoadFloat3(&light.position)) * camera.GetViewProjection() @@ -4597,11 +4597,11 @@ void DrawLightVisualizers(const CameraComponent& camera, GRAPHICSTHREAD threadID VolumeLightCB lcb; lcb.lightColor = XMFLOAT4(light.color.x, light.color.y, light.color.z, 1); - lcb.lightEnerdis = XMFLOAT4(light.energy, light.range, light.fov, light.energy); + lcb.lightEnerdis = XMFLOAT4(light.energy, light.GetRange(), light.fov, light.energy); if (type == LightComponent::POINT) { - lcb.lightEnerdis.w = light.range*light.energy*0.01f; // scale + lcb.lightEnerdis.w = light.GetRange()*light.energy*0.01f; // scale XMStoreFloat4x4(&lcb.lightWorld, XMMatrixTranspose( XMMatrixScaling(lcb.lightEnerdis.w, lcb.lightEnerdis.w, lcb.lightEnerdis.w)* camrot* @@ -4615,7 +4615,7 @@ void DrawLightVisualizers(const CameraComponent& camera, GRAPHICSTHREAD threadID else if (type == LightComponent::SPOT) { float coneS = (float)(light.fov / 0.7853981852531433); - lcb.lightEnerdis.w = light.range*light.energy*0.03f; // scale + lcb.lightEnerdis.w = light.GetRange()*light.energy*0.03f; // scale XMStoreFloat4x4(&lcb.lightWorld, XMMatrixTranspose( XMMatrixScaling(coneS*lcb.lightEnerdis.w, lcb.lightEnerdis.w, coneS*lcb.lightEnerdis.w)* XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation))* @@ -4737,7 +4737,7 @@ void DrawVolumeLights(const CameraComponent& camera, GRAPHICSTHREAD threadID) { MiscCB miscCb; miscCb.g_xColor.x = float(entityArrayOffset_Lights + i); - float sca = light.range + 1; + float sca = light.GetRange() + 1; XMStoreFloat4x4(&miscCb.g_xTransform, XMMatrixTranspose(XMMatrixScaling(sca, sca, sca)*XMMatrixTranslationFromVector(XMLoadFloat3(&light.position)) * camera.GetViewProjection())); device->UpdateBuffer(&constantBuffers[CBTYPE_MISC], &miscCb, threadID); device->BindConstantBuffer(VS, &constantBuffers[CBTYPE_MISC], CB_GETBINDSLOT(MiscCB), threadID); @@ -4752,7 +4752,7 @@ void DrawVolumeLights(const CameraComponent& camera, GRAPHICSTHREAD threadID) miscCb.g_xColor.x = float(entityArrayOffset_Lights + i); const float coneS = (const float)(light.fov / XM_PIDIV4); XMStoreFloat4x4(&miscCb.g_xTransform, XMMatrixTranspose( - XMMatrixScaling(coneS*light.range, light.range, coneS*light.range)* + XMMatrixScaling(coneS*light.GetRange(), light.GetRange(), coneS*light.GetRange())* XMMatrixRotationQuaternion(XMLoadFloat4(&light.rotation))* XMMatrixTranslationFromVector(XMLoadFloat3(&light.position)) * camera.GetViewProjection() @@ -5059,7 +5059,7 @@ void DrawForShadowMap(const CameraComponent& camera, GRAPHICSTHREAD threadID, ui SHCAM shcam; CreateSpotLightShadowCam(light, shcam); - const float zFarP = max(1.0f, light.range); + const float zFarP = max(1.0f, light.GetRange()); Frustum frustum; frustum.Create(shcam.realProjection, shcam.View, zFarP); @@ -5137,7 +5137,7 @@ void DrawForShadowMap(const CameraComponent& camera, GRAPHICSTHREAD threadID, ui for (size_t i = 0; i < scene.aabb_objects.GetCount(); ++i) { const AABB& aabb = scene.aabb_objects[i]; - if (SPHERE(light.position, light.range).intersects(aabb)) + if (SPHERE(light.position, light.GetRange()).intersects(aabb)) { const ObjectComponent& object = scene.objects[i]; if (object.IsRenderable() && object.IsCastingShadow() && object.GetRenderTypes() == RENDERTYPE_OPAQUE) @@ -5171,7 +5171,7 @@ void DrawForShadowMap(const CameraComponent& camera, GRAPHICSTHREAD threadID, ui device->BindConstantBuffer(PS, &constantBuffers[CBTYPE_MISC], CB_GETBINDSLOT(MiscCB), threadID); const float zNearP = 0.1f; - const float zFarP = max(1.0f, light.range); + const float zFarP = max(1.0f, light.GetRange()); SHCAM cameras[] = { SHCAM(XMFLOAT4(0.5f, -0.5f, -0.5f, -0.5f), zNearP, zFarP, XM_PIDIV2), //+x SHCAM(XMFLOAT4(0.5f, 0.5f, 0.5f, -0.5f), zNearP, zFarP, XM_PIDIV2), //-x diff --git a/WickedEngine/wiSceneSystem.cpp b/WickedEngine/wiSceneSystem.cpp index c604d823d..fe1f88332 100644 --- a/WickedEngine/wiSceneSystem.cpp +++ b/WickedEngine/wiSceneSystem.cpp @@ -1245,7 +1245,7 @@ namespace wiSceneSystem LightComponent& light = lights.Create(entity); light.energy = energy; - light.range = range; + light.range_local = range; light.fov = XM_PIDIV4; light.color = color; light.SetType(LightComponent::POINT); @@ -1269,7 +1269,7 @@ namespace wiSceneSystem ForceFieldComponent& force = forces.Create(entity); force.gravity = 0; - force.range = 0; + force.range_local = 0; force.type = ENTITY_TYPE_FORCEFIELD_POINT; return entity; @@ -1972,7 +1972,7 @@ namespace wiSceneSystem XMStoreFloat3(&force.position, T); XMStoreFloat3(&force.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, -1, 0, 0), W))); - force.range = force.range_local * max(XMVectorGetX(S), max(XMVectorGetY(S), XMVectorGetZ(S))); + force.range_global = force.range_local * max(XMVectorGetX(S), max(XMVectorGetY(S), XMVectorGetZ(S))); }); } void RunLightUpdateSystem( @@ -1998,7 +1998,7 @@ namespace wiSceneSystem XMStoreFloat4(&light.rotation, R); XMStoreFloat3(&light.direction, XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W)); - light.range = light.range_local * max(XMVectorGetX(S), max(XMVectorGetY(S), XMVectorGetZ(S))); + light.range_global = light.range_local * max(XMVectorGetX(S), max(XMVectorGetY(S), XMVectorGetZ(S))); switch (light.type) { @@ -2006,10 +2006,10 @@ namespace wiSceneSystem aabb.createFromHalfWidth(wiRenderer::GetCamera().Eye, XMFLOAT3(10000, 10000, 10000)); break; case LightComponent::SPOT: - aabb.createFromHalfWidth(light.position, XMFLOAT3(light.range, light.range, light.range)); + aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange())); break; case LightComponent::POINT: - aabb.createFromHalfWidth(light.position, XMFLOAT3(light.range, light.range, light.range)); + aabb.createFromHalfWidth(light.position, XMFLOAT3(light.GetRange(), light.GetRange(), light.GetRange())); break; case LightComponent::SPHERE: case LightComponent::DISC: diff --git a/WickedEngine/wiSceneSystem.h b/WickedEngine/wiSceneSystem.h index c31790dcb..9c72c239c 100644 --- a/WickedEngine/wiSceneSystem.h +++ b/WickedEngine/wiSceneSystem.h @@ -662,7 +662,7 @@ namespace wiSceneSystem // Non-serialized attributes: XMFLOAT3 position; - float range; + float range_global; XMFLOAT3 direction; XMFLOAT4 rotation; XMFLOAT3 front; @@ -681,7 +681,7 @@ namespace wiSceneSystem inline bool IsVisualizerEnabled() const { return _flags & VISUALIZER; } inline bool IsStatic() const { return _flags & LIGHTMAPONLY_STATIC; } - inline float GetRange() const { return range; } + inline float GetRange() const { return range_global; } inline void SetType(LightType val) { type = val; @@ -792,9 +792,11 @@ namespace wiSceneSystem // Non-serialized attributes: XMFLOAT3 position; - float range; + float range_global; XMFLOAT3 direction; + inline float GetRange() const { return range_global; } + void Serialize(wiArchive& archive, uint32_t seed = 0); }; @@ -881,6 +883,7 @@ namespace wiSceneSystem inline bool IsPlaying() const { return _flags & PLAYING; } inline bool IsLooped() const { return _flags & LOOPED; } inline float GetLength() const { return end - start; } + inline bool IsEnded() const { return timer >= end; } inline void Play() { _flags |= PLAYING; } inline void Pause() { _flags &= ~PLAYING; } diff --git a/WickedEngine/wiSceneSystem_BindLua.cpp b/WickedEngine/wiSceneSystem_BindLua.cpp index 83e2d10af..bcbc0b4f6 100644 --- a/WickedEngine/wiSceneSystem_BindLua.cpp +++ b/WickedEngine/wiSceneSystem_BindLua.cpp @@ -990,6 +990,7 @@ Luna::FunctionType AnimationComponent_BindLua::metho lunamethod(AnimationComponent_BindLua, SetLooped), lunamethod(AnimationComponent_BindLua, IsLooped), lunamethod(AnimationComponent_BindLua, IsPlaying), + lunamethod(AnimationComponent_BindLua, IsEnded), { NULL, NULL } }; Luna::PropertyType AnimationComponent_BindLua::properties[] = { @@ -1048,6 +1049,11 @@ int AnimationComponent_BindLua::IsPlaying(lua_State* L) wiLua::SSetBool(L, component->IsPlaying()); return 1; } +int AnimationComponent_BindLua::IsEnded(lua_State* L) +{ + wiLua::SSetBool(L, component->IsEnded()); + return 1; +} diff --git a/WickedEngine/wiSceneSystem_BindLua.h b/WickedEngine/wiSceneSystem_BindLua.h index 68433f6d8..34888409d 100644 --- a/WickedEngine/wiSceneSystem_BindLua.h +++ b/WickedEngine/wiSceneSystem_BindLua.h @@ -155,6 +155,7 @@ namespace wiSceneSystem_BindLua int SetLooped(lua_State* L); int IsLooped(lua_State* L); int IsPlaying(lua_State* L); + int IsEnded(lua_State* L); }; class MaterialComponent_BindLua diff --git a/models/Havoc/havoc.wiscene b/models/Havoc/havoc.wiscene index d8c27158d64f0d5454889a5cf9424d6cca777ff8..fa61746b43bf960d2cbee53cef2037f6269b2ce2 100644 GIT binary patch delta 106169 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 delta 105958 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 frameStart and self.frame <= frameEnd + end, + require_animationfinish = function(self) -- animation is finished + return scene.Component_GetAnimation(self.states[self.state].anim).IsEnded() + end, - -- Common requirement conditions for state transitions: - require_input = function(self, value) -- player input notation - return self.input_string == value - end, - require_window = function(self, a, b) -- frame window range - return self.frame >= a and self.frame <= b - end, + -- List all possible states: + states = { + Idle = { + anim_name = "Idle", + anim = INVALID_ENTITY, + }, + Walk_Backward = { + anim_name = "Back", + anim = INVALID_ENTITY, + update = function(self) + self.force = vector.Add(self.force, Vector(-0.025 * self.face, 0)) + end, + }, + Walk_Forward = { + anim_name = "Forward", + anim = INVALID_ENTITY, + update = function(self) + self.force = vector.Add(self.force, Vector(0.025 * self.face, 0)) + end, + }, + Jump = { + anim_name = "Jump", + anim = INVALID_ENTITY, + looped = false, + update = function(self) + if(self.frame == 0) then + self.force = vector.Add(self.force, Vector(0, 0.8)) + end + end, + }, + JumpBack = { + anim_name = "Jump", + anim = INVALID_ENTITY, + looped = false, + update = function(self) + if(self.frame == 0) then + self.force = vector.Add(self.force, Vector(-0.2 * self.face, 0.8)) + end + end, + }, + JumpForward = { + anim_name = "Jump", + anim = INVALID_ENTITY, + looped = false, + update = function(self) + if(self.frame == 0) then + self.force = vector.Add(self.force, Vector(0.2 * self.face, 0.8)) + end + end, + }, + FallStart = { + anim_name = "FallStart", + anim = INVALID_ENTITY, + looped = false, + }, + Fall = { + anim_name = "Fall", + anim = INVALID_ENTITY, + }, + FallEnd = { + anim_name = "FallEnd", + anim = INVALID_ENTITY, + looped = false, + }, + CrouchStart = { + anim_name = "CrouchStart", + anim = INVALID_ENTITY, + looped = false, + }, + Crouch = { + anim_name = "Crouch", + anim = INVALID_ENTITY, + }, + CrouchEnd = { + anim_name = "CrouchEnd", + anim = INVALID_ENTITY, + looped = false, + }, + Turn = { + anim_name = "Turn", + anim = INVALID_ENTITY, + looped = false, + update = function(self) + if(self.frame == 0) then + self.face = self.request_face + end + end, + }, + + LightPunch = { + anim_name = "LightPunch", + anim = INVALID_ENTITY, + looped = false, + }, + ForwardLightPunch = { + anim_name = "FLightPunch", + anim = INVALID_ENTITY, + looped = false, + }, + HeavyPunch = { + anim_name = "HeavyPunch", + anim = INVALID_ENTITY, + looped = false, + }, + LowPunch = { + anim_name = "LowPunch", + anim = INVALID_ENTITY, + looped = false, + }, + LightKick = { + anim_name = "LightKick", + anim = INVALID_ENTITY, + looped = false, + }, + HeavyKick = { + anim_name = "HeavyKick", + anim = INVALID_ENTITY, + looped = false, + }, + LowKick = { + anim_name = "LowKick", + anim = INVALID_ENTITY, + looped = false, + }, + Uppercut = { + anim_name = "Uppercut", + anim = INVALID_ENTITY, + looped = false, + }, + Shoryuken = { + anim_name = "Shoryuken", + anim = INVALID_ENTITY, + looped = false, + update = function(self) + if(self:require_frame(0)) then + self.force = vector.Add(self.force, Vector(0.3 * self.face, 0.9)) + end + end, + }, + }, - -- State machine describes all possible state transitions: - statemachine = { - Idle = { - { "Walk_Forward", condition = function(self) return self:require_input("6") end, }, - { "Walk_Backward", condition = function(self) return self:require_input("4") end, }, - { "Jump", condition = function(self) return self:require_input("8") end, }, - { "JumpBack", condition = function(self) return self:require_input("7") end, }, - { "JumpForward", condition = function(self) return self:require_input("9") end, }, - { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, + -- State machine describes all possible state transitions: + -- StateFrom = { + -- { "StateTo1", condition = function(self) return [requirements that should be met] end }, + -- { "StateTo2", condition = function(self) return [requirements that should be met] end }, + -- } + statemachine = { + Idle = { + { "Turn", condition = function(self) return self.request_face ~= self.face and self:require_input("5") end, }, + { "Walk_Forward", condition = function(self) return self:require_input("6") end, }, + { "Walk_Backward", condition = function(self) return self:require_input("4") end, }, + { "Jump", condition = function(self) return self:require_input("8") end, }, + { "JumpBack", condition = function(self) return self:require_input("7") end, }, + { "JumpForward", condition = function(self) return self:require_input("9") end, }, + { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, + { "LightPunch", condition = function(self) return self:require_input("5A") end, }, + { "HeavyPunch", condition = function(self) return self:require_input("5B") end, }, + { "LightKick", condition = function(self) return self:require_input("5C") end, }, + }, + Walk_Backward = { + { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, + { "Walk_Forward", condition = function(self) return self:require_input("6") end, }, + { "JumpBack", condition = function(self) return self:require_input("7") end, }, + { "Idle", condition = function(self) return self:require_input("5") end, }, + { "LightPunch", condition = function(self) return self:require_input("5A") end, }, + { "HeavyPunch", condition = function(self) return self:require_input("5B") end, }, + { "LightKick", condition = function(self) return self:require_input("5C") end, }, + { "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, }, + { "HeavyKick", condition = function(self) return self:require_input("6C") end, }, + }, + Walk_Forward = { + { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, + { "Walk_Backward", condition = function(self) return self:require_input("4") end, }, + { "JumpForward", condition = function(self) return self:require_input("9") end, }, + { "Idle", condition = function(self) return self:require_input("5") end, }, + { "LightPunch", condition = function(self) return self:require_input("5A") end, }, + { "HeavyPunch", condition = function(self) return self:require_input("5B") end, }, + { "LightKick", condition = function(self) return self:require_input("5C") end, }, + { "ForwardLightPunch", condition = function(self) return self:require_input("6A") end, }, + { "HeavyKick", condition = function(self) return self:require_input("6C") end, }, + }, + Jump = { + { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, + }, + JumpForward = { + { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, + }, + JumpBack = { + { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, + }, + FallStart = { + { "FallEnd", condition = function(self) return self.position.GetY() <= 0.5 end, }, + { "Fall", condition = function(self) return self:require_animationfinish() end, }, + }, + Fall = { + { "FallEnd", condition = function(self) return self.position.GetY() <= 0.5 end, }, + }, + FallEnd = { + { "Idle", condition = function(self) return self.position.GetY() <= 0 and self:require_animationfinish() end, }, + }, + CrouchStart = { + { "Idle", condition = function(self) return self:require_input("5") end, }, + { "Crouch", condition = function(self) return (self:require_input("1") or self:require_input("2") or self:require_input("3")) and self:require_animationfinish() end, }, + }, + Crouch = { + { "CrouchEnd", condition = function(self) return self:require_input("5") or self:require_input("4") or self:require_input("6") or self:require_input("7") or self:require_input("8") or self:require_input("9") end, }, + { "LowPunch", condition = function(self) return self:require_input("2A") or self:require_input("1A") or self:require_input("3A") end, }, + { "LowKick", condition = function(self) return self:require_input("2C") or self:require_input("1C") or self:require_input("3C") end, }, + { "Uppercut", condition = function(self) return self:require_input("2B") or self:require_input("1B") or self:require_input("3B") end, }, + { "Shoryuken", condition = function(self) return self:require_input("2D") end, }, + }, + CrouchEnd = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + Turn = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + LightPunch = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + ForwardLightPunch = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + HeavyPunch = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + LowPunch = { + { "Crouch", condition = function(self) return self:require_animationfinish() end, }, + }, + LightKick = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + HeavyKick = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + LowKick = { + { "Crouch", condition = function(self) return self:require_animationfinish() end, }, + }, + Uppercut = { + { "Idle", condition = function(self) return self:require_animationfinish() end, }, + }, + Shoryuken = { + { "FallStart", condition = function(self) return self:require_animationfinish() end, }, + }, }, - Walk_Backward = { - { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, - { "Walk_Forward", condition = function(self) return self:require_input("6") end, }, - { "JumpBack", condition = function(self) return self:require_input("7") end, }, - { "Idle", condition = function(self) return self:require_input("5") end, }, - }, - Walk_Forward = { - { "CrouchStart", condition = function(self) return self:require_input("1") or self:require_input("2") or self:require_input("3") end, }, - { "Walk_Backward", condition = function(self) return self:require_input("4") end, }, - { "JumpForward", condition = function(self) return self:require_input("9") end, }, - { "Idle", condition = function(self) return self:require_input("5") end, }, - }, - Jump = { - { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, - }, - JumpForward = { - { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, - }, - JumpBack = { - { "FallStart", condition = function(self) return self.velocity.GetY() <= 0 end, }, - }, - FallStart = { - { "FallEnd", condition = function(self) return self.position.GetY() <= 0.5 end, }, - { "Fall", condition = function(self) return self:require_window(3,3) end, }, - }, - Fall = { - { "FallEnd", condition = function(self) return self.position.GetY() <= 0.5 end, }, - }, - FallEnd = { - { "Idle", condition = function(self) return self.position.GetY() <= 0 and self:require_window(3,3) end, }, - }, - CrouchStart = { - { "Idle", condition = function(self) return self:require_input("5") end, }, - { "Crouch", condition = function(self) return (self:require_input("1") or self:require_input("2") or self:require_input("3")) and self:require_window(3,3) end, }, - }, - Crouch = { - { "CrouchEnd", condition = function(self) return self:require_input("5") end, }, - }, - CrouchEnd = { - { "Idle", condition = function(self) return self:require_window(3,3) end, }, - }, - }, - state = "Idle", -- starting state + state = "Idle", -- starting state + - -- Ends the current state: - EndState = function(self) - scene.Component_GetAnimation(self.states[self.state].anim).Stop() - end, - -- Starts a new state: - StartState = function(self, dst_state) - scene.Component_GetAnimation(self.states[dst_state].anim).Play() - self.frame = 0 - self.state = dst_state - end, - -- Parse state machine at current state and perform transition if applicable: - StepStateMachine = function(self) - local transition_candidates = self.statemachine[self.state] - if(transition_candidates ~= nil) then - for i,dst in pairs(transition_candidates) do - -- check transition requirement conditions: - local requirements_met = true - if(dst.condition ~= nil) then - requirements_met = dst.condition(self) - end - if(requirements_met) then - -- transition to new state when all requirements are met: - self:EndState() - self:StartState(dst[1]) - return - end - end - end - end, - - - Create = function(self, face, shirt_color) - - -- Load the model into a custom scene: - -- We use a custom scene because if two models are loaded into the global scene, they will have name collisions - -- and thus we couldn't properly query entities by name - local model_scene = Scene() - self.model = LoadModel(model_scene, "../models/havoc/havoc.wiscene") - - self.face = face - - local shirt_material_entity = model_scene.Entity_FindByName("material_shirt") - model_scene.Component_GetMaterial(shirt_material_entity).SetBaseColor(shirt_color) - - -- Verify states by printing them to backlog: - backlog_post("\nStates:") - for i,state in pairs(self.states) do - state.anim = model_scene.Entity_FindByName(state.anim_name) - if(state.looped ~= nil) then - model_scene.Component_GetAnimation(state.anim).SetLooped(state.looped) - end - backlog_post(i .. ": " .. state.anim_name .. " (" .. state.anim .. ")") - for property,value in pairs(state) do - backlog_post("\t" .. property) - end - end - - -- Verify state machine transitions by printing them to backlog: - backlog_post("\nState machine:") - for src,transition_candidates in pairs(self.statemachine) do - -- Print source state: - backlog_post(src .. ": ") - - -- Print destination states: - for i,dst in pairs(transition_candidates) do - -- Print destination state name: - backlog_post("\t" .. i .. ": " .. dst[1]) - - -- Print destination state requirements: - if(dst.require ~= nil) then - for type,values in pairs(dst.require) do - local values_string = "" - for i,value in ipairs(values) do - values_string = values_string .. value .. "; " - end - backlog_post("\t\t" .. type .. " = {" .. values_string .. "}") + -- Ends the current state: + EndState = function(self) + scene.Component_GetAnimation(self.states[self.state].anim).Stop() + end, + -- Starts a new state: + StartState = function(self, dst_state) + scene.Component_GetAnimation(self.states[dst_state].anim).Play() + self.frame = 0 + self.state = dst_state + end, + -- Parse state machine at current state and perform transition if applicable: + StepStateMachine = function(self) + local transition_candidates = self.statemachine[self.state] + if(transition_candidates ~= nil) then + for i,dst in pairs(transition_candidates) do + -- check transition requirement conditions: + local requirements_met = true + if(dst.condition ~= nil) then + requirements_met = dst.condition(self) + end + if(requirements_met) then + -- transition to new state when all requirements are met: + self:EndState() + self:StartState(dst[1]) + return end end end - end - - -- Move the custom scene into the global scene: - scene.Merge(model_scene) - - self:StartState(self.state) - - end, - - Update = function(self) - local model_transform = scene.Component_GetTransform(self.model) - self.position = model_transform.GetPosition() - - self.frame = self.frame + 1 - - -- read input: - local left = input.Down(VK_LEFT) - local right = input.Down(VK_RIGHT) - local up = input.Down(VK_UP) - local down = input.Down(VK_DOWN) - - self.input_string = "" - if(up and left) then - self.input_string = "7" - elseif(up and right) then - self.input_string = "9" - elseif(up) then - self.input_string = "8" - elseif(down and left) then - self.input_string = "1" - elseif(down and right) then - self.input_string = "3" - elseif(down) then - self.input_string = "2" - elseif(left) then - self.input_string = "4" - elseif(right) then - self.input_string = "6" - else - self.input_string = "5" - end - - self:StepStateMachine() - - -- Call current state's update method if there is one: - local current_state = self.states[self.state] - if(current_state ~= nil) then - if(current_state.update ~= nil) then - current_state.update(self) + end, + -- Execute the currently active state: + ExecuteCurrentState = function(self) + local current_state = self.states[self.state] + if(current_state ~= nil) then + if(current_state.update ~= nil) then + current_state.update(self) + end end - end - - -- force from gravity: - self.force = vector.Add(self.force, Vector(0,-0.04,0)) + end, + - -- apply force: - self.velocity = vector.Add(self.velocity, self.force) - self.force = Vector() + Create = function(self, face, shirt_color) - -- aerial drag: - self.velocity = vector.Multiply(self.velocity, 0.98) + -- Load the model into a custom scene: + -- We use a custom scene because if two models are loaded into the global scene, they will have name collisions + -- and thus we couldn't properly query entities by name + local model_scene = Scene() + self.model = LoadModel(model_scene, "../models/havoc/havoc.wiscene") + + -- Place model according to starting facing direction: + self.face = face + self.request_face = face + self.position = Vector(self.face * -4) + + -- Set shirt color todifferentiate between characters: + local shirt_material_entity = model_scene.Entity_FindByName("material_shirt") + model_scene.Component_GetMaterial(shirt_material_entity).SetBaseColor(shirt_color) - -- apply velocity: - model_transform.Translate(self.velocity) - model_transform.UpdateTransform() + -- Initialize states: + for i,state in pairs(self.states) do + state.anim = model_scene.Entity_FindByName(state.anim_name) + if(state.looped ~= nil) then + model_scene.Component_GetAnimation(state.anim).SetLooped(state.looped) + end + end + + -- Move the custom scene into the global scene: + scene.Merge(model_scene) + + self:StartState(self.state) + + end, + + AI = function(self) + -- todo some AI bot behaviour + table.insert(self.input_buffer, {age = 0, command = "5"}) + end, + + Input = function(self) + + local input_string = "" + + -- read input: + local left = input.Down(string.byte('A')) + local right = input.Down(string.byte('D')) + local up = input.Down(string.byte('W')) + local down = input.Down(string.byte('S')) + local A = input.Press(VK_RIGHT) + local B = input.Press(VK_UP) + local C = input.Press(VK_LEFT) + local D = input.Press(VK_DOWN) + + -- swap left and right if facing is opposite side: + if(self.face < 0) then + local tmp = right + right = left + left = tmp + end + + if(up and left) then + input_string = "7" + elseif(up and right) then + input_string = "9" + elseif(up) then + input_string = "8" + elseif(down and left) then + input_string = "1" + elseif(down and right) then + input_string = "3" + elseif(down) then + input_string = "2" + elseif(left) then + input_string = "4" + elseif(right) then + input_string = "6" + else + input_string = "5" + end + + if(A) then + input_string = input_string .. "A" + end + if(B) then + input_string = input_string .. "B" + end + if(C) then + input_string = input_string .. "C" + end + if(D) then + input_string = input_string .. "D" + end + + + table.insert(self.input_buffer, {age = 0, command = input_string}) + + end, + + Update = function(self) + self.frame = self.frame + 1 + + self:StepStateMachine() + self:ExecuteCurrentState() + + -- Manage input buffer: + for i,element in pairs(self.input_buffer) do + element.age = element.age + 1 + end + if(#self.input_buffer > 120) then + table.remove(self.input_buffer, 1) + end + + -- force from gravity: + self.force = vector.Add(self.force, Vector(0,-0.04,0)) + + -- apply force: + self.velocity = vector.Add(self.velocity, self.force) + self.force = Vector() + + -- aerial drag: + self.velocity = vector.Multiply(self.velocity, 0.98) + + -- apply velocity: + self.position = vector.Add(self.position, self.velocity) + + -- check if we are below or on the ground: + if(self.position.GetY() <= 0 and self.velocity.GetY()<=0) then + self.position.SetY(0) -- snap to ground + self.velocity.SetY(0) -- don't fall below ground + self.velocity = vector.Multiply(self.velocity, 0.8) -- ground drag: + end + + -- Transform component gets set as absolute coordinates every frame: + local model_transform = scene.Component_GetTransform(self.model) + model_transform.ClearTransform() + model_transform.Translate(self.position) + model_transform.Rotate(Vector(0, 3.1415 * ((self.face - 1) * 0.5))) + model_transform.UpdateTransform() + + -- Some debug draw: + DrawPoint(model_transform.GetPosition(), 0.1, Vector(1,0,0,1)) + DrawLine(model_transform.GetPosition(),model_transform.GetPosition():Add(self.velocity), Vector(0,1,0,1)) + DrawLine(model_transform.GetPosition(),model_transform.GetPosition():Add(Vector(self.face)), Vector(0,0,1,1)) - -- check if we are below or on the ground: - local posY = model_transform.GetPosition().GetY() - if(posY <= 0 and self.velocity.GetY()<=0) then - model_transform.Translate(Vector(0,-posY,0)) -- snap to ground - self.velocity.SetY(0) -- don't fall below ground - self.velocity = vector.Multiply(self.velocity, 0.8) -- ground drag: end - + + } + + self:Create(face, shirt_color) + return self +end + + +-- script camera state: +local camera_position = Vector() +local camera_transform = TransformComponent() + +-- Interaction between two characters: +local ResolveCharacters = function(player1, player2) + + -- Facing direction requests: + if(player1.position.GetX() < player2.position.GetX()) then + player1.request_face = 1 + player2.request_face = -1 + else + player1.request_face = -1 + player2.request_face = 1 end -} --- Player Controller -local player1 = Character_Havoc + -- Camera: + local CAMERA_HEIGHT = 4 -- camera height from ground + local DEFAULT_CAMERADISTANCE = -9.5 -- the default camera distance when characters are close to each other + local MODIFIED_CAMERADISTANCE = -11.5 -- if the two players are far enough from each other, the camera will zoom out to this distance + local CAMERA_DISTANCE_MODIFIER = 10 -- the required distance between the characters when the camera should zoom out + local XBOUNDS = 20 -- play area horizontal bounds + local CAMERA_SIDE_LENGTH = 11 -- play area inside the camera (character can't move outside camera even if inside the play area) + + -- Clamp the players inside the camera: + local camera_side_left = camera_position.GetX() - CAMERA_SIDE_LENGTH + local camera_side_right = camera_position.GetX() + CAMERA_SIDE_LENGTH + player1.position.SetX(math.clamp(player1.position.GetX(), camera_side_left, camera_side_right)) + player2.position.SetX(math.clamp(player2.position.GetX(), camera_side_left, camera_side_right)) + + local camera_position_new = Vector() + local distanceX = math.abs(player1.position.GetX() - player2.position.GetX()) + local distanceY = math.abs(player1.position.GetY() - player2.position.GetY()) + + -- camera height: + if(player1.position.GetY() > 4 or player2.position.GetY() > 4) then + camera_position_new.SetY( math.min(player1.position.GetY(), player2.position.GetY()) + distanceY ) + else + camera_position_new.SetY(CAMERA_HEIGHT) + end + + -- camera distance: + if(distanceX > CAMERA_DISTANCE_MODIFIER) then + camera_position_new.SetZ(MODIFIED_CAMERADISTANCE) + else + camera_position_new.SetZ(DEFAULT_CAMERADISTANCE) + end + + -- camera horizontal position: + local centerX = math.clamp((player1.position.GetX() + player2.position.GetX()) * 0.5, -XBOUNDS, XBOUNDS) + camera_position_new.SetX(centerX) + + -- smooth camera: + camera_position = vector.Lerp(camera_position, camera_position_new, 0.1) + + -- finally update the global camera with current values: + camera_transform.ClearTransform() + camera_transform.Translate(camera_position) + camera_transform.UpdateTransform() + GetCamera().TransformCamera(camera_transform) + +end -- Main loop: runProcess(function() ClearWorld() + + -- Fighting game needs stable frame rate and deterministic controls at all times. We will also refer to frames in this script instead of time units. + -- We lock the framerate to 60 FPS, so if frame rate goes below, game will play slower + -- + -- There is also the possibility to implement game logic in fixed_update() instead, but that is not common for fighting games + main.SetTargetFrameRate(60) + main.SetFrameRateLock(true) -- We will override the render path so we can invoke the script from Editor and controls won't collide with editor scripts -- Also save the active component that we can restore when ESCAPE is pressed @@ -324,10 +568,7 @@ runProcess(function() local path = RenderPath3D_TiledForward() main.SetActivePath(path) - main.SetTargetFrameRate(60) - main.SetFrameRateLock(true) - - local font = Font("This script is showcasing how to write a simple fighting game.\nControls:\n#####################\narrows: walk\nNumPad: actions\nESCAPE key: quit\nR: reload script"); + local font = Font("This script is showcasing how to write a simple fighting game.\nControls:\n#####################\nWASD: move\narrows: actions (ABCD)\nESCAPE key: quit\nR: reload script"); font.SetSize(20) font.SetPos(Vector(10, GetScreenHeight() - 10)) font.SetAlign(WIFALIGN_LEFT, WIFALIGN_BOTTOM) @@ -341,14 +582,24 @@ runProcess(function() info.SetAlign(WIFALIGN_LEFT, WIFALIGN_CENTER) info.SetShadowColor(Vector(0,0,0,1)) path.AddFont(info) + + LoadModel("../models/dojo.wiscene") - player1:Create(1, Vector(1,1,1,1)) + -- Create the two player characters. Parameters are facing direction and shirt material color to differentiate between them: + local player1 = Character(1, Vector(1,1,1,1)) -- facing to right, white shirt + local player2 = Character(-1, Vector(1,0,0,1)) -- facing to left, red shirt while true do - player1:Update() + player1:Input() + player2:AI() - info.SetText("input_string = " .. player1.input_string .."\nstate = " .. player1.state .. "\nframe = " .. player1.frame) + player1:Update() + player2:Update() + + ResolveCharacters(player1, player2) + + info.SetText("state = " .. player1.state .. "\nframe = " .. player1.frame) -- Wait for Engine update tick update() @@ -374,29 +625,3 @@ runProcess(function() end end) - - --- Draw -runProcess(function() - - while true do - - -- Do some debug draw geometry: - - -- If backlog is opened, skip debug draw: - while(backlog_isactive()) do - waitSeconds(1) - end - - local model_transform = scene.Component_GetTransform(player1.model) - - DrawPoint(model_transform.GetPosition(), 0.1, Vector(1,0,0,1)) - DrawLine(model_transform.GetPosition(),model_transform.GetPosition():Add(player1.velocity), Vector(0,1,0,1)) - DrawLine(model_transform.GetPosition(),model_transform.GetPosition():Add(Vector(player1.face)), Vector(0,0,1,1)) - - - -- Wait for the engine to render the scene - render() - end -end) -