Playable scripted NinjaSnowWar.

Disabled garbage collection from CScriptArray again because of some very odd "objects not getting deleted" bugs.
Unprepare script contexts before garbage collection.
This commit is contained in:
Lasse Öörni 2011-02-01 22:51:45 +00:00
parent c73c30fde1
commit bb67b6d7fe
21 changed files with 480 additions and 120 deletions

View File

@ -45,7 +45,7 @@
<EnemySpawnRate>1</EnemySpawnRate>
<EnemySpawnPosition>0 1000 12000</EnemySpawnPosition>
<EnemySpawnOffset>4000</EnemySpawnOffset>
<EnemySpawnVelocity>0 1000 -2500</EnemySpawnVelocity>
<EnemySpawnVelocity>0 1000 -3000</EnemySpawnVelocity>
<PowerUpSpawnRate>15</PowerUpSpawnRate>
<PowerUpSpawnOffset>4000</PowerUpSpawnOffset>
<PowerUpSpawnHeight>5000</PowerUpSpawnHeight>

View File

@ -0,0 +1,174 @@
const float initialAggression = 0.0025;
const float initialPrediction = 3000;
const float initialAimSpeed = 5;
const float deltaAggression = 0.00005;
const float deltaPrediction = -20;
const float deltaAimSpeed = 0.15;
const float maxAggression = 0.01;
const float maxPrediction = 2000;
const float maxAimSpeed = 20;
float aiAggression = initialAggression;
float aiPrediction = initialPrediction;
float aiAimSpeed = initialAimSpeed;
void resetAI()
{
aiAggression = initialAggression;
aiPrediction = initialPrediction;
aiAimSpeed = initialAimSpeed;
}
void makeAIHarder()
{
aiAggression += deltaAggression;
if (aiAggression > maxAggression)
aiAggression = maxAggression;
aiPrediction += deltaPrediction;
if (aiPrediction < maxPrediction)
aiPrediction = maxPrediction;
aiAimSpeed += deltaAimSpeed;
if (aiAimSpeed > maxAimSpeed)
aiAimSpeed = maxAimSpeed;
}
class AIController
{
void control(Ninja@ ninja, Entity@ entity, float timeStep)
{
Scene@ scene = entity.getScene();
RigidBody@ body = entity.getComponent("RigidBody");
// Get closest ninja on the player's side
Entity@ targetEntity;
Ninja@ targetNinja;
RigidBody@ targetBody;
array<Entity@> entities = scene.getScriptedEntities("Ninja");
float closestDistance = M_INFINITY;
for (uint i = 0; i < entities.length(); ++i)
{
Ninja@ otherNinja = cast<Ninja>(entities[i].getScriptObject());
RigidBody@ otherBody = entities[i].getComponent("RigidBody");
if (otherNinja.side == SIDE_PLAYER)
{
float distance = (body.getPhysicsPosition() - otherBody.getPhysicsPosition()).getLength();
if (distance < closestDistance)
{
@targetEntity = entities[i];
@targetNinja = otherNinja;
@targetBody = otherBody;
closestDistance = distance;
}
}
}
if ((@targetEntity != null) && (targetNinja.health > 0))
{
ninja.controls.set(CTRL_FIRE, false);
ninja.controls.set(CTRL_JUMP, false);
float deltaX = 0.0f;
float deltaY = 0.0f;
// Aim from own head to target's feet
Vector3 ownPos(body.getPhysicsPosition() + Vector3(0, 90, 0));
Vector3 targetPos(targetBody.getPhysicsPosition() + Vector3(0, -90, 0));
float distance = (targetPos - ownPos).getLength();
// Use prediction according to target distance & estimated snowball speed
Vector3 currentAim(ninja.getAim() * Vector3(0, 0, 1));
float predictDistance = distance;
if (predictDistance > 5000) predictDistance = 5000;
Vector3 predictedPos = targetPos + targetBody.getLinearVelocity() * predictDistance / aiPrediction;
Vector3 targetAim = (predictedPos - ownPos);
// Add distance/height compensation
float compensation = max(targetAim.getLength() - 1500, 0);
targetAim += Vector3(0, 0.005, 0) * (compensation * compensation);
// X-aiming
targetAim.normalize();
Vector3 currentYaw(currentAim.x, 0, currentAim.z);
Vector3 targetYaw(targetAim.x, 0, targetAim.z);
currentYaw.normalize();
targetYaw.normalize();
deltaX = clamp(Quaternion(currentYaw, targetYaw).getYaw(), -aiAimSpeed, aiAimSpeed);
// Y-aiming
Vector3 currentPitch(0, currentAim.y, 1);
Vector3 targetPitch(0, targetAim.y, 1);
currentPitch.normalize();
targetPitch.normalize();
deltaY = clamp(Quaternion(currentPitch, targetPitch).getPitch(), -aiAimSpeed, aiAimSpeed);
ninja.controls.yaw += 0.1 * deltaX;
ninja.controls.pitch += 0.1 * deltaY;
// Firing? if close enough and relatively correct aim
if ((distance < 2500) && (currentAim.dotProduct(targetAim) > 0.75))
{
if (random(1.0) < aiAggression)
ninja.controls.set(CTRL_FIRE, true);
}
// Movement
ninja.dirChangeTime -= timeStep;
if (ninja.dirChangeTime <= 0)
{
ninja.dirChangeTime = 0.5 + random(1.0);
ninja.controls.set(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT, false);
// Far distance: go forward
if (distance > 3000)
ninja.controls.set(CTRL_UP, true);
else if (distance > 600)
{
// Medium distance: random strafing, predominantly forward
float v = random(1.0);
if (v < 0.8)
ninja.controls.set(CTRL_UP, true);
float h = random(1.0);
if (h < 0.3)
ninja.controls.set(CTRL_LEFT, true);
if (h > 0.7)
ninja.controls.set(CTRL_RIGHT, true);
}
else
{
// Close distance: random strafing backwards
float v = random(1.0);
if (v < 0.8)
ninja.controls.set(CTRL_DOWN, true);
float h = random(1.0);
if (h < 0.4)
ninja.controls.set(CTRL_LEFT, true);
if (h > 0.6)
ninja.controls.set(CTRL_RIGHT, true);
}
}
// Random jump, if going forward
if ((ninja.controls.isDown(CTRL_UP)) && (distance < 1000))
{
if (random(1.0) < (aiAggression / 5.0))
ninja.controls.set(CTRL_JUMP, true);
}
}
else
{
// If no target, walk idly
ninja.controls.set(CTRL_ALL, false);
ninja.controls.set(CTRL_UP, true);
ninja.dirChangeTime -= timeStep;
if (ninja.dirChangeTime <= 0)
{
ninja.dirChangeTime = 1 + random(2.0);
ninja.controls.yaw += 0.1 * (random(600.0) - 300.0);
}
if (ninja.isSliding)
ninja.controls.yaw += 0.2;
}
}
}

View File

@ -12,8 +12,6 @@ const int SIDE_ENEMY = 2;
class GameObject : ScriptObject
{
Controls controls;
Controls prevControls;
bool onGround;
bool isSliding;
float duration;
@ -37,11 +35,6 @@ class GameObject : ScriptObject
{
}
void setControls(const Controls&in newControls)
{
controls = newControls;
}
void updateFixed(float timeStep)
{
// Disappear when duration expired

View File

@ -1,4 +1,5 @@
#include "Scripts/GameObject.as"
#include "Scripts/AIController.as"
const int ANIM_MOVE = 1;
const int ANIM_ATTACK = 2;
@ -14,9 +15,13 @@ const Vector3 ninjaThrowPosition(0, 20, 100);
const float ninjaThrowDelay = 0.1;
const float ninjaDrawDistance = 15000;
const float ninjaCorpseDuration = 3;
const int ninjaPoints = 250;
class Ninja : GameObject
{
Controls controls;
Controls prevControls;
AIController@ controller;
bool okToJump;
bool smoke;
float inAirTime;
@ -101,6 +106,10 @@ class Ninja : GameObject
return;
}
// AI control if controller exists
if (@controller != null)
controller.control(this, entity, timeStep);
RigidBody@ body = entity.getComponent("RigidBody");
AnimationController@ controller = entity.getComponent("AnimationController");
@ -275,6 +284,12 @@ class Ninja : GameObject
deathDir = 1;
playSound("Sounds/SmallExplosion.wav");
VariantMap eventData;
eventData["Points"] = ninjaPoints;
eventData["DamageSide"] = lastDamageSide;
sendEvent("Points", eventData);
sendEvent("Kill", eventData);
}
deathTime += timeStep;

View File

@ -1,4 +1,5 @@
// Partial remake of NinjaSnowWar in script
// Remake of NinjaSnowWar in script
// Does not support load/save, or multiplayer yet.
#include "Scripts/LightFlash.as"
#include "Scripts/Ninja.as"
@ -10,7 +11,13 @@ const float mouseSensitivity = 0.125;
const float cameraMinDist = 25;
const float cameraMaxDist = 500;
const float cameraSafetyDist = 30;
const int initialMaxEnemies = 5;
const int finalMaxEnemies = 25;
const int maxPowerups = 5;
const int incrementEach = 10;
const int playerHealth = 20;
const float enemySpawnRate = 1;
const float powerupSpawnRate = 15;
Scene@ gameScene;
Camera@ gameCamera;
@ -21,8 +28,15 @@ BorderImage@ healthBar;
BorderImage@ sight;
Controls playerControls;
Controls prevPlayerControls;
bool paused = false;
bool gameOn = false;
int score = 0;
int hiscore = 0;
int maxEnemies = 0;
int incrementCounter = 0;
float enemySpawnTimer = 0;
float powerupSpawnTimer = 0;
void start()
{
@ -34,7 +48,10 @@ void start()
startGame();
subscribeToEvent("Update", "handleUpdate");
subscribeToEvent("PhysicsPreStep", "handleFixedUpdate");
subscribeToEvent("PostUpdate", "handlePostUpdate");
subscribeToEvent("Points", "handlePoints");
subscribeToEvent("Kill", "handleKill");
subscribeToEvent("KeyDown", "handleKeyDown");
subscribeToEvent("WindowResized", "handleWindowResized");
}
@ -155,16 +172,29 @@ void startGame()
{
// Clear the scene of all existing scripted entities
array<Entity@> scriptedEntities = gameScene.getScriptedEntities();
for (int i = 0; i < scriptedEntities.length(); ++i)
for (uint i = 0; i < scriptedEntities.length(); ++i)
gameScene.removeEntity(scriptedEntities[i]);
Entity@ playerEntity = gameScene.createEntity("Player");
GameObject@ object = cast<GameObject>(playerEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
object.create(Vector3(0, 90, 0), Quaternion());
object.health = object.maxHealth = playerHealth;
object.side = SIDE_PLAYER;
Ninja@ playerNinja = cast<Ninja>(playerEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
playerNinja.create(Vector3(0, 90, 0), Quaternion());
playerNinja.health = playerNinja.maxHealth = playerHealth;
playerNinja.side = SIDE_PLAYER;
// Make sure the player can not shoot on first frame by holding the button down
playerNinja.controls = playerNinja.prevControls = playerControls;
resetAI();
gameOn = true;
score = 0;
maxEnemies = initialMaxEnemies;
incrementCounter = 0;
enemySpawnTimer = 0;
powerupSpawnTimer = 0;
playerControls.yaw = 0;
playerControls.pitch = 0;
messageText.setText("");
}
void handleUpdate(StringHash eventType, VariantMap& eventData)
@ -174,7 +204,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
if (input.getKeyPress(KEY_F2))
engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
if ((!console.isVisible()) && (input.getKeyPress('P')))
if ((!console.isVisible()) && (input.getKeyPress('P')) && (gameOn))
{
paused = !paused;
if (paused)
@ -187,20 +217,122 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
updateControls();
}
void handleFixedUpdate(StringHash eventType, VariantMap& eventData)
{
// Check that scene being updated matches (we have only one scene, but for completeness...)
if (@eventData["Scene"].getScene() != @gameScene)
return;
float timeStep = eventData["TimeStep"].getFloat();
// Spawn new objects and check for end/restart of game
spawnObjects(timeStep);
checkEndAndRestart();
}
void handlePostUpdate(StringHash eventType, VariantMap& eventData)
{
updateCamera();
updateStatus();
}
void handlePoints(StringHash eventType, VariantMap& eventData)
{
if (eventData["DamageSide"].getInt() == SIDE_PLAYER)
{
score += eventData["Points"].getInt();
if (score > hiscore)
hiscore = score;
}
}
void handleKill(StringHash eventType, VariantMap& eventData)
{
if (eventData["DamageSide"].getInt() == SIDE_PLAYER)
{
makeAIHarder();
// Increment amount of simultaneous enemies after enough kills
incrementCounter++;
if (incrementCounter >= incrementEach)
{
incrementCounter = 0;
if (maxEnemies < finalMaxEnemies)
maxEnemies++;
}
}
}
void spawnObjects(float timeStep)
{
// Spawn powerups
powerupSpawnTimer += timeStep;
if (powerupSpawnTimer >= powerupSpawnRate)
{
powerupSpawnTimer = 0;
//int numPowerups = gameScene.getScriptedEntities("SnowCrate").length() + gameScene.getScriptedEntities("Potion").length();
int numPowerups = 0;
if (numPowerups < maxPowerups)
{
const float maxOffset = 4000;
float xOffset = random(maxOffset * 2.0f) - maxOffset;
float zOffset = random(maxOffset * 2.0f) - maxOffset;
Vector3 position(xOffset, 5000, zOffset);
Entity@ crateEntity = gameScene.createEntity();
GameObject@ crateObject = cast<GameObject>(crateEntity.createScriptObject("Scripts/NinjaSnowWar.as", "SnowCrate"));
crateObject.create(position, Quaternion());
}
}
// Spawn enemies
enemySpawnTimer += timeStep;
if (enemySpawnTimer > enemySpawnRate)
{
enemySpawnTimer = 0;
// Take the player ninja into account
int numEnemies = gameScene.getScriptedEntities("Ninja").length() - 1;
if (numEnemies < maxEnemies)
{
const float maxOffset = 4000;
float offset = random(maxOffset * 2.0) - maxOffset;
// Random north/east/south/west direction
int dir = randomInt() & 3;
dir *= 90;
Quaternion q(dir, Vector3(0, 1, 0));
Vector3 position(q * Vector3(offset, 1000, 12000));
Entity@ enemyEntity = gameScene.createEntity();
Ninja@ enemyNinja = cast<Ninja>(enemyEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
enemyNinja.create(position, q);
enemyNinja.side = SIDE_ENEMY;
@enemyNinja.controller = AIController();
RigidBody@ enemyBody = enemyEntity.getComponent("RigidBody");
enemyBody.setLinearVelocity(q * Vector3(0, 1000, -3000));
}
}
}
void checkEndAndRestart()
{
if ((gameOn) && (@gameScene.getEntity("Player") == null))
{
gameOn = false;
messageText.setText("Press Fire or Jump to restart!");
return;
}
if ((!gameOn) && (playerControls.isPressed(CTRL_FIRE | CTRL_JUMP, prevPlayerControls)))
startGame();
}
void updateControls()
{
prevPlayerControls = playerControls;
playerControls.set(CTRL_ALL, false);
Entity@ playerEntity = gameScene.getEntity("Player");
if (@playerEntity == null)
return;
if (!console.isVisible())
{
if (input.getKeyDown('W'))
@ -226,11 +358,12 @@ void updateControls()
playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
playerControls.pitch = clamp(playerControls.pitch, -60, 60);
GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());
object.setControls(playerControls);
if ((input.getKeyPress('K')) && (object.health > 0))
object.health = object.health - 1;
Entity@ playerEntity = gameScene.getEntity("Player");
if (@playerEntity != null)
{
Ninja@ playerNinja = cast<Ninja>(playerEntity.getScriptObject());
playerNinja.controls = playerControls;
}
}
void updateCamera()
@ -268,6 +401,9 @@ void updateStatus()
if (engine.isHeadless())
return;
scoreText.setText("Score " + score);
hiscoreText.setText("Hiscore " + hiscore);
Entity@ playerEntity = gameScene.getEntity("Player");
if (@playerEntity == null)
return;

View File

@ -4,6 +4,7 @@ const int snowcrateHealth = 5;
const float snowcrateMass = 200;
const float snowcrateFriction = 0.35;
const float snowcrateDrawDistance = 15000;
const int snowcratePoints = 250;
class SnowCrate : GameObject
{
@ -47,6 +48,11 @@ class SnowCrate : GameObject
spawnParticleEffect(body.getPhysicsPosition(), "Particle/SnowExplosionBig.xml", 2);
spawnObject(body.getPhysicsPosition(), Quaternion(), "Potion");
scene.removeEntity(entity);
VariantMap eventData;
eventData["Points"] = snowcratePoints;
eventData["DamageSide"] = lastDamageSide;
sendEvent("Points", eventData);
}
}
}

View File

@ -95,19 +95,8 @@ Engine::Engine(const std::string& windowTitle, const std::string& logFileName, b
mCache = new ResourceCache();
// Register the inbuilt events as local only
static const std::string inbuiltEvents[] = {
"Update", "PostUpdate", "PostRenderUpdate", "ClientIdentity", "ClientJoinedScene", "ClientLeftScene", "ClientControls",
"ClientDisconnected", "GotSceneInfoList", "JoinedScene", "JoinSceneFailed", "FileTransferCompleted", "FileTransferFailed",
"ControlsUpdate", "ControlsPlayback", "MouseButtonDown", "MouseButtonUp", "MouseMove", "KeyDown", "KeyUp", "Char",
"PeerConnected", "NetworkPacket", "PeerDisconnected", "PhysicsPreStep", "PhysicsPostStep", "PhysicsCollision",
"EntityCollision", "WindowMessage", "WindowResized", "BeginFrame", "EndFrame", "SceneUpdate", "ScenePostUpdate",
"AsyncLoadProgress", "AsyncLoadFinished", "TryFocus", "Focused", "Defocused", "Pressed", "Toggled", "SliderChanged",
"ViewChanged", "TextChanged", "TextFinished", "ItemSelected", ""
};
for (unsigned i = 0; inbuiltEvents[i].length(); ++i)
registerLocalOnlyEvent(inbuiltEvents[i]);
// Register the engine and library events as local only
resetLocalOnlyEvents();
sInstance = this;
}
@ -277,9 +266,14 @@ void Engine::runFrame(Scene* scene, Camera* camera, bool updateScene)
{
PROFILE(Engine_RunFrame);
// Get frame timestep / update / render
float timeStep = getNextTimeStep();
update(timeStep, scene, camera, updateScene);
render();
// Perform script garbage collection outside everything else
if (mScriptEngine)
mScriptEngine->garbageCollect(false);
}
mProfiler->endFrame();
@ -372,6 +366,24 @@ DebugHud* Engine::createDebugHud()
return mDebugHud;
}
void Engine::resetLocalOnlyEvents()
{
static const std::string inbuiltEvents[] = {
"Update", "PostUpdate", "PostRenderUpdate", "ClientIdentity", "ClientJoinedScene", "ClientLeftScene", "ClientControls",
"ClientDisconnected", "GotSceneInfoList", "JoinedScene", "JoinSceneFailed", "FileTransferCompleted", "FileTransferFailed",
"ControlsUpdate", "ControlsPlayback", "MouseButtonDown", "MouseButtonUp", "MouseMove", "KeyDown", "KeyUp", "Char",
"PeerConnected", "NetworkPacket", "PeerDisconnected", "PhysicsPreStep", "PhysicsPostStep", "PhysicsCollision",
"EntityCollision", "WindowMessage", "WindowResized", "BeginFrame", "EndFrame", "SceneUpdate", "ScenePostUpdate",
"AsyncLoadProgress", "AsyncLoadFinished", "TryFocus", "Focused", "Defocused", "Pressed", "Toggled", "SliderChanged",
"ViewChanged", "TextChanged", "TextFinished", "ItemSelected", ""
};
// First clear existing registrations, then register the inbuild events
removeAllLocalOnlyEvents();
for (unsigned i = 0; inbuiltEvents[i].length(); ++i)
registerLocalOnlyEvent(inbuiltEvents[i]);
}
void Engine::setDefaultScene(Scene* scene)
{
mDefaultScene = scene;

View File

@ -90,6 +90,8 @@ public:
void removeClient();
//! Remove the server subsystem
void removeServer();
//! Clear previous and register the default local only events
void resetLocalOnlyEvents();
//! Set the default scene. This will always be available as the "scene" global property to scripting
void setDefaultScene(Scene* scene);
//! Set minimum frames per second. If FPS goes lower than this, time will appear to slow down

View File

@ -435,6 +435,7 @@ static void registerEngine(asIScriptEngine* engine)
engine->RegisterObjectMethod("Engine", "DebugHud@+ createDebugHud()", asMETHOD(Engine, createDebugHud), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void removeClient()", asMETHOD(Engine, removeClient), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void removeServer()", asMETHOD(Engine, removeServer), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void resetLocalOnlyEvents()", asMETHOD(Engine, resetLocalOnlyEvents), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void setDefaultScene(Scene@+)", asMETHOD(Engine, setDefaultScene), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void setMinFps(int)", asMETHOD(Engine, setMinFps), asCALL_THISCALL);
engine->RegisterObjectMethod("Engine", "void setMaxFps(int)", asMETHOD(Engine, setMaxFps), asCALL_THISCALL);

View File

@ -64,6 +64,9 @@ static float ATan2(float y, float x)
static void registerMathFunctions(asIScriptEngine* engine)
{
engine->RegisterGlobalProperty("const float M_INFINITY", (void*)&M_INFINITY);
engine->RegisterGlobalProperty("const float M_EPSILON", (void*)&M_EPSILON);
engine->RegisterGlobalFunction("float sin(float)", asFUNCTION(Sin), asCALL_CDECL);
engine->RegisterGlobalFunction("float cos(float)", asFUNCTION(Cos), asCALL_CDECL);
engine->RegisterGlobalFunction("float tan(float)", asFUNCTION(Tan), asCALL_CDECL);

View File

@ -92,10 +92,15 @@ void registerLocalOnlyEvent(StringHash eventType)
void registerLocalOnlyEvent(const std::string& name)
{
registerHash(name);
registerHash(name, false);
localOnlyEvents.insert(StringHash(name));
}
void removeAllLocalOnlyEvents()
{
localOnlyEvents.clear();
}
bool checkRemoteEvent(StringHash eventType)
{
return localOnlyEvents.find(eventType) == localOnlyEvents.end();

View File

@ -89,6 +89,8 @@ struct RemoteEvent
void registerLocalOnlyEvent(StringHash eventType);
//! Register an event to be only sent locally
void registerLocalOnlyEvent(const std::string& name);
//! Remove all registered local only events
void removeAllLocalOnlyEvents();
//! Check if an event is allowed to be sent remotely
bool checkRemoteEvent(StringHash eventType);
//! Return remote event sender connection. Only non-null during the event handling

View File

@ -179,8 +179,8 @@ CScriptArray::CScriptArray(asUINT length, asIObjectType* ot)
CreateBuffer(&buffer, length);
// Notify the GC of the successful creation
if (objType->GetFlags() & asOBJ_GC)
objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
//if (objType->GetFlags() & asOBJ_GC)
// objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
}
CScriptArray::CScriptArray(asUINT length, void *defVal, asIObjectType *ot)
@ -211,8 +211,8 @@ CScriptArray::CScriptArray(asUINT length, void *defVal, asIObjectType *ot)
CreateBuffer(&buffer, length);
// Notify the GC of the successful creation
if (objType->GetFlags() & asOBJ_GC)
objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
//if (objType->GetFlags() & asOBJ_GC)
// objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
// Initialize the elements with the default value
for (asUINT n = 0; n < GetSize(); ++n)
@ -519,27 +519,27 @@ void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src)
}
// GC behaviour
void CScriptArray::EnumReferences(asIScriptEngine* engine)
{
// If the array is holding handles, then we need to notify the GC of them
int typeId = objType->GetSubTypeId();
if (typeId & asTYPEID_MASK_OBJECT)
{
void** d = (void**)buffer->data;
for (asUINT n = 0; n < buffer->numElements; ++n)
{
if (d[n])
engine->GCEnumCallback(d[n]);
}
}
}
//void CScriptArray::EnumReferences(asIScriptEngine* engine)
//{
// // If the array is holding handles, then we need to notify the GC of them
// int typeId = objType->GetSubTypeId();
// if (typeId & asTYPEID_MASK_OBJECT)
// {
// void** d = (void**)buffer->data;
// for (asUINT n = 0; n < buffer->numElements; ++n)
// {
// if (d[n])
// engine->GCEnumCallback(d[n]);
// }
// }
//}
// GC behaviour
void CScriptArray::ReleaseAllHandles(asIScriptEngine* engine)
{
// Resizing to zero will release everything
Resize(0);
}
//void CScriptArray::ReleaseAllHandles(asIScriptEngine* engine)
//{
// // Resizing to zero will release everything
// Resize(0);
//}
void CScriptArray::AddRef() const
{
@ -557,27 +557,27 @@ void CScriptArray::Release() const
}
// GC behaviour
int CScriptArray::GetRefCount()
{
return refCount;
}
//int CScriptArray::GetRefCount()
//{
// return refCount;
//}
// GC behaviour
void CScriptArray::SetFlag()
{
gcFlag = true;
}
//void CScriptArray::SetFlag()
//{
// gcFlag = true;
//}
// GC behaviour
bool CScriptArray::GetFlag()
{
return gcFlag;
}
//bool CScriptArray::GetFlag()
//{
// return gcFlag;
//}
// Registers the template array type
void registerArray(asIScriptEngine* engine)
{
engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE);
engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_TEMPLATE);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in)", asFUNCTIONPR(ScriptArrayFactory, (asIObjectType*), CScriptArray*), asCALL_CDECL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in, uint)", asFUNCTIONPR(ScriptArrayFactory2, (asIObjectType*, asUINT), CScriptArray*), asCALL_CDECL);
@ -594,10 +594,10 @@ void registerArray(asIScriptEngine* engine)
engine->RegisterObjectMethod("array<T>", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL);
engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
engine->RegisterObjectMethod("array<T>", "void resize(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
//engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
//engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
//engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
//engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
//engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
engine->RegisterDefaultArrayType("array<T>");
}

View File

@ -28,6 +28,7 @@
#include "RegisterArray.h"
#include "RegisterStdString.h"
#include "ScriptEngine.h"
#include "ScriptFile.h"
#include "StringUtils.h"
#include <angelscript.h>
@ -128,14 +129,14 @@ void ScriptEngine::garbageCollect(bool fullCycle)
{
PROFILE(Script_GarbageCollect);
// Run either a number of iterations, or a full cycle if requested
if (fullCycle)
mAngelScriptEngine->GarbageCollect(asGC_FULL_CYCLE);
else
{
for (unsigned i = 0; i < GARBAGE_COLLECT_ITERATIONS; ++i)
mAngelScriptEngine->GarbageCollect(asGC_ONE_STEP);
}
// Unprepare contexts up to the highest used
mImmediateContext->Unprepare();
unsigned highest = getHighestScriptNestingLevel();
for (unsigned i = 0; i < highest; ++i)
mScriptFileContexts[i]->Unprepare();
// Then actually garbage collect
mAngelScriptEngine->GarbageCollect(fullCycle ? asGC_FULL_CYCLE : asGC_ONE_STEP);
}
void ScriptEngine::setLogMode(ScriptLogMode mode)

View File

@ -40,9 +40,6 @@ enum ScriptLogMode
//! Maximum function/method nesting level
static const unsigned MAX_SCRIPT_NESTING_LEVEL = 32;
//! Amount of garbage collection iterations when not doing a full cycle
static const unsigned GARBAGE_COLLECT_ITERATIONS = 5;
//! Utilizes the AngelScript library for executing scripts
class ScriptEngine : public RefCounted
{

View File

@ -37,7 +37,8 @@
#include "DebugNew.h"
static int scriptNestingLevel = 0;
static unsigned scriptNestingLevel = 0;
static unsigned highestScriptNestingLevel = 0;
ScriptFile* lastScriptFile = 0;
ScriptFile::ScriptFile(ScriptEngine* scriptEngine, const std::string& name) :
@ -59,13 +60,13 @@ ScriptFile::~ScriptFile()
for (std::vector<ScriptInstance*>::iterator i = instances.begin(); i != instances.end(); ++i)
(*i)->releaseObject();
// Perform a full garbage collection cycle, also clean up contexts which might still refer to the module's functions
mScriptEngine->garbageCollect(true);
// Remove the module
asIScriptEngine* engine = mScriptEngine->getAngelScriptEngine();
engine->DiscardModule(getName().c_str());
mScriptModule = 0;
// Perform a full garbage collection cycle
mScriptEngine->garbageCollect(true);
}
if (lastScriptFile == this)
lastScriptFile = 0;
@ -103,12 +104,12 @@ void ScriptFile::load(Deserializer& source, ResourceCache* cache)
mScriptEngine->setLogMode(LOGMODE_RETAINED);
mScriptEngine->clearLogMessages();
int result = mScriptModule->Build();
std::string errors = mScriptEngine->getLogMessages();
mScriptEngine->setLogMode(LOGMODE_IMMEDIATE);
if (result < 0)
{
std::string errors = mScriptEngine->getLogMessages();
EXCEPTION("Failed to compile script module " + getName() + ":\n" + errors);
}
if (!errors.empty())
LOGWARNING(errors);
LOGINFO("Compiled script module " + getName());
mCompiled = true;
@ -170,6 +171,8 @@ bool ScriptFile::execute(asIScriptFunction* function, const std::vector<Variant>
setParameters(context, function, parameters);
++scriptNestingLevel;
if (scriptNestingLevel > highestScriptNestingLevel)
highestScriptNestingLevel = scriptNestingLevel;
bool success = context->Execute() >= 0;
--scriptNestingLevel;
@ -212,6 +215,8 @@ bool ScriptFile::execute(asIScriptObject* object, asIScriptFunction* method, con
setParameters(context, method, parameters);
++scriptNestingLevel;
if (scriptNestingLevel > highestScriptNestingLevel)
highestScriptNestingLevel = scriptNestingLevel;
bool success = context->Execute() >= 0;
--scriptNestingLevel;
@ -530,3 +535,10 @@ unsigned getScriptNestingLevel()
{
return scriptNestingLevel;
}
unsigned getHighestScriptNestingLevel()
{
unsigned ret = highestScriptNestingLevel;
highestScriptNestingLevel = 0;
return ret;
}

View File

@ -116,5 +116,7 @@ private:
ScriptFile* getLastScriptFile();
//! Get current script execution nesting level
unsigned getScriptNestingLevel();
//! Return highest script execution nesting level last frame, and clear
unsigned getHighestScriptNestingLevel();
#endif // SCRIPT_SCRIPTFILE_H

View File

@ -217,7 +217,7 @@ void AIController::control(Ninja* ninja, float time)
}
else
{
// If not, walk idly
// If no target, walk idly
ninja->mControls.set(CTRL_ALL, false);
ninja->mControls.set(CTRL_UP, true);
ninja->mDirChangeTime -= time;

View File

@ -918,7 +918,7 @@ void Game::spawnObjects(float timeStep)
float maxOffset = GameConfig::getReal("Game/EnemySpawnOffset");
float offset = random(maxOffset * 2.0f) - maxOffset;
// Random north/east/south/west direction
int dir = random(4);
int dir = rand() & 3;
dir *= 90;
Quaternion q((float)dir, Vector3::sUp);
Vector3 position(q * (GameConfig::getVector3("Game/EnemySpawnPosition") + Vector3(offset, 0, 0)));

View File

@ -38,10 +38,6 @@
Application::Application()
{
std::string userDir = getUserDocumentsDirectory();
std::string applicationDir = userDir + "Urho3D";
createDirectory(applicationDir);
}
Application::~Application()
@ -56,20 +52,11 @@ Application::~Application()
void Application::run()
{
init();
// Create application directory
std::string applicationDir = getUserDocumentsDirectory() + "Urho3D";
createDirectory(applicationDir);
while (!mEngine->isExiting())
{
if (!mScriptFile->execute("void runFrame()"))
EXCEPTION("Failed to execute the runFrame() function");
// Script garbage collection
mEngine->getScriptEngine()->garbageCollect(false);
}
}
void Application::init()
{
// Parse script file name from the command line
const std::vector<std::string>& arguments = getArguments();
std::string scriptFileName;
if ((arguments.size()) && (arguments[0][0] != '-'))
@ -78,12 +65,11 @@ void Application::init()
EXCEPTION("Usage: Urho3D <scriptfile> [options]\n\n"
"The script file should implement the functions void start() and void runFrame(). See the readme for options.\n");
// Instantiate the engine
mEngine = new Engine();
// Add resources
mCache = mEngine->getResourceCache();
if (fileExists("CoreData.pak"))
mCache->addPackageFile(new PackageFile("CoreData.pak"));
if (fileExists("Data.pak"))
mCache->addPackageFile(new PackageFile("Data.pak"));
else
@ -91,6 +77,7 @@ void Application::init()
mCache->addResourcePath(getSystemFontDirectory());
// Initialize engine & scripting
mEngine->init(arguments);
mEngine->createScriptEngine();
@ -100,4 +87,11 @@ void Application::init()
throw Exception(getLog()->getLastMessage(), false);
if (!mScriptFile->execute("void start()"))
EXCEPTION("Failed to execute the start() function");
// Run until exited
while (!mEngine->isExiting())
{
if (!mScriptFile->execute("void runFrame()"))
EXCEPTION("Failed to execute the runFrame() function");
}
}

View File

@ -31,19 +31,24 @@ class Engine;
class ResourceCache;
class ScriptFile;
//! Urho3D Shell application
class Application
{
public:
//! Construct
Application();
//! Destruct
~Application();
//! Run the script-based initialization & main loop
void run();
private:
void init();
//! Engine
SharedPtr<Engine> mEngine;
//! Resource cache
SharedPtr<ResourceCache> mCache;
//! Script file
SharedPtr<ScriptFile> mScriptFile;
};