Алгоритмы, Наработки и Способности
Способ реализации:
Jass
Тип:
Способность
Версия Warcraft:
1.26
Заклинание с движением огненного шара для новичков и не только. С правильным рассчётом в движении и плавностью.

Видео

Код

globals    
    hashtable HT = InitHashtable()
    group Group = CreateGroup()

    unit Caster
    unit Dummy
    unit Target
    
    real Angle
    real CasterX
    real CasterY
    real DummyX
    real DummyY
    
    timer Timer
    integer TimerId
    integer Tick
    real Time
    real TimeEase //Переменная для просчёта плавности

    constant real Fireball_TimerPeriod = 0.03
    constant real Fireball_Distance = 1000
    constant real Fireball_Speed = 900
    constant real Fireball_Range = 250
    constant real Fireball_Damage = 3
    constant integer Fireball_EffectLimit = 3
endglobals

native UnitAlive takes unit id returns boolean

function Fireball_Group takes nothing returns nothing
    set Target = GetEnumUnit()
    
    if UnitAlive( Target ) and IsUnitEnemy( Target, GetOwningPlayer( Caster ) ) and not IsUnitType( Target, UNIT_TYPE_STRUCTURE ) then
        call UnitDamageTarget( Caster, Target, Fireball_Damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNKNOWN, WEAPON_TYPE_WHOKNOWS )
    endif
endfunction

function Fireball_Timer takes nothing returns nothing
    set Timer = GetExpiredTimer()
    set TimerId = GetHandleId( Timer )
    
    set Caster = LoadUnitHandle( HT, TimerId, 'cstr' )
    set Dummy = LoadUnitHandle( HT, TimerId, 'dumm' )
    set Tick = LoadInteger( HT, TimerId, 'tick' )
    
    set Time = ( Tick * Fireball_TimerPeriod ) / ( Fireball_Distance / Fireball_Speed ) //Пройденное время делится на общее время
    set TimeEase = Time * Time //Для плавности, если она не нужна, то удалить строчку, удалить переменную TimeEase, и ниже везде в 2 функциях заменить эту переменную на Time
    //set TimeEase = Sin( Time * bj_PI ) / 2  //Ещё одна приколюха :)
    set DummyX = LoadReal( HT, TimerId, 'minX' ) * ( 1 - TimeEase ) + LoadReal( HT, TimerId, 'maxX' ) * TimeEase //Для правильного движения фаерболла по оси X
    set DummyY = LoadReal( HT, TimerId, 'minY' ) * ( 1 - TimeEase ) + LoadReal( HT, TimerId, 'maxY' ) * TimeEase //Тоже самое но с Y
    
    if GetRectMinX( bj_mapInitialPlayableArea ) <= DummyX and DummyX <= GetRectMaxX( bj_mapInitialPlayableArea ) and GetRectMinY( bj_mapInitialPlayableArea ) <= DummyY and DummyY <= GetRectMaxY( bj_mapInitialPlayableArea ) then //Условие что-бы фаерболл не вылетел за карту, и не произошёл Fatal Error
        call SetUnitX( Dummy, DummyX )
        call SetUnitY( Dummy, DummyY )
        
        if Tick == ( Tick / Fireball_EffectLimit ) * Fireball_EffectLimit then //Ограничение по эффектам, менять как и всё остальное сверху в константах
            call DestroyEffect( AddSpecialEffect( "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl", DummyX, DummyY ) )
            call DestroyEffect( AddSpecialEffect( "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl", DummyX, DummyY ) )
        endif
    endif
    
    call GroupEnumUnitsInRange( Group, DummyX, DummyY, Fireball_Range, null )
    call ForGroup( Group, function Fireball_Group )
    call GroupClear( Group )
    
    call SaveInteger( HT, TimerId, 'tick', Tick + 1 )
    
    if Time >= 1 then
        call RemoveUnit( Dummy )
        call PauseTimer( Timer )
        call DestroyTimer( Timer )
        call FlushChildHashtable( HT, TimerId )
    endif
endfunction

function Fireball_Actions takes nothing returns nothing
    if GetSpellAbilityId() == 'A000' then
        set Caster = GetTriggerUnit()
        set CasterX = GetUnitX( Caster )
        set CasterY = GetUnitY( Caster )
        
        set Angle = Atan2( GetSpellTargetY() - CasterY, GetSpellTargetX() - CasterX ) //Просчитывается угол от позиции кастера в сторону каста
        set CasterX = CasterX + 50 * Cos( Angle ) //Просчитывается смещение в сторону ранее просчитанного угла
        set CasterY = CasterY + 50 * Sin( Angle ) //Тоже что и CasterX но CasterY
        
        set Timer = CreateTimer()
        set TimerId = GetHandleId( Timer )
        
        call SaveUnitHandle( HT, TimerId, 'cstr', Caster )
        call SaveUnitHandle( HT, TimerId, 'dumm', CreateUnit( GetTriggerPlayer(), 'h001', CasterX, CasterY, Angle ) )
        call SaveReal( HT, TimerId, 'minX', CasterX )
        call SaveReal( HT, TimerId, 'minY', CasterY )        
        call SaveReal( HT, TimerId, 'maxX', CasterX + Fireball_Distance * Cos( Angle ) ) //Тут сохраняется координата X, но смещённая на + 1000 в сторону каста
        call SaveReal( HT, TimerId, 'maxY', CasterY + Fireball_Distance * Sin( Angle ) ) //Тоже самое что и X но Y
        call SaveInteger( HT, TimerId, 'tick', 0 )
        call TimerStart( Timer, Fireball_TimerPeriod, true, function Fireball_Timer )
    endif
endfunction

function InitTrig_Fireball takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    loop
        call TriggerRegisterPlayerUnitEvent( t, Player( i ), EVENT_PLAYER_UNIT_SPELL_EFFECT, null )
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
    
    call TriggerAddAction( t, function Fireball_Actions )  
    set t = null
    
    call FogEnable( false )
    call FogMaskEnable( false )
endfunction

Требования

Скопировать код из шапки карты и триггера со спеллом к себе на карту, если такие глобальные переменные уже имеются, то копировать не надо. Поменять равкод способности в константах если он другой (скорее всего другой). Скопировать импортированные файлы к себе на карту.
`
LOADING AD...
The comment is deleted
20
Похожая либа для проджектайлов на Zinc
library StaticProjectiles requires TechUtils, Parabolas {

	// Constants
	constant real FOCUS_LOST_DISTANCE = 200.;
	constant real HIT_DISTANCE = 25.;

	struct StaticProjectile {
		integer object;
		unit owner;
		unit target;
		unit projectile;
		integer actionsId;
		real speed;
		real offsetZ;
		real startOffsetZ;
		boolean canFocusLost;
		string func;
		Point2D current;
		Point2D end;
		Parabola parabola;
		boolean isFocused;

		timer tm;
		static integer CK_HID = 0;
		static real INTERVAL = 1./40.;

		static method create(
			integer object,
			unit owner,
			unit target,
			unit projectile,
			integer actionsId,
			string func,
			real speed,
			real maxHeight,
			real offsetZ,
			boolean canFocusLost
		) -> StaticProjectile {
			StaticProjectile this = StaticProjectile.allocate();
			this.current = Point2D.createAtWidget(owner);
			this.end = Point2D.createAtWidget(target);
			this.parabola = Parabola.create(Point2D.clone(current), Point2D.clone(end), maxHeight/(1200/current.distTo(end)));

			this.object = object;
			this.owner = owner;
			this.target = target;
			this.projectile = projectile;
			this.speed = speed;
			this.offsetZ = offsetZ;
			this.actionsId = actionsId;
			this.func = func;
			this.canFocusLost = canFocusLost;

			this.startOffsetZ = GetUnitZ(owner) - current.getTerrainZ();
			this.isFocused = true;
			MakeUnitFly(projectile);
			SetUnitPathing(projectile, false);

			this.tm = CreateTimer();
			SaveInteger(HT, GetHandleId(this.tm), CK_HID, this);
			TimerStart(this.tm, 0, false, function StaticProjectile.callback);

			return this;
		}

		static method createAt(
			integer object,
			unit owner,
			real x,
			real y,
			unit projectile,
			integer actionsId,
			string func,
			real speed,
			real maxHeight,
			real offsetZ
		) -> StaticProjectile {
			StaticProjectile this = StaticProjectile.allocate();
			this.current = Point2D.createAtWidget(owner);
			this.end = Point2D.createAt(x, y);
			if (maxHeight > 0) {
				this.parabola = Parabola.create(Point2D.clone(current), Point2D.clone(end), maxHeight/(1200/current.distTo(end)));
			} else {
				this.parabola = 0;
			}
			

			this.object = object;
			this.owner = owner;
			this.target = null;
			this.projectile = projectile;
			this.speed = speed;
			this.offsetZ = offsetZ;
			this.actionsId = actionsId;
			this.func = func;

			this.startOffsetZ = GetUnitZ(owner) - current.getTerrainZ();
			this.isFocused = false;
			MakeUnitFly(projectile);
			SetUnitPathing(projectile, false);

			this.tm = CreateTimer();
			SaveInteger(HT, GetHandleId(this.tm), CK_HID, this);
			TimerStart(this.tm, 0, false, function StaticProjectile.callback);

			return this;
		}

		method destroy() {
			KillUnit(projectile);
			owner = null;
			target = null;
			projectile = null;

			current.destroy();
			end.destroy();
			if (IsSet(parabola)) parabola.destroy();

			FlushChildHashtable(HT, GetHandleId(tm));
			PauseTimer(tm);
			DestroyTimer(tm);
			tm = null;

			this.deallocate();
		}

		static method callback() {
			StaticProjectile this = LoadInteger(HT, GetHandleId(GetExpiredTimer()), CK_HID);
			real angle;
			real endOffsetZ = 0;
			
			TimerStart(tm, INTERVAL, false, function StaticProjectile.callback);

			if (isFocused && canFocusLost && end.distToWidget(target) >= FOCUS_LOST_DISTANCE)
				isFocused = false;
			
			if (isFocused) {
				end.setAtWidget(target);
				endOffsetZ = GetUnitZ(target) - end.getTerrainZ();
				if (IsSet(parabola)) parabola.setEnd(end);
			}

			angle = current.angleTo(end);
			current.movePolar(speed * INTERVAL, angle);
		
			SetUnitFacing(projectile, angle);
			SetUnitX(projectile, current.x);
			SetUnitY(projectile, current.y);
			if (IsSet(parabola)) SetUnitZ(projectile, parabola.calculateZ(current, offsetZ + startOffsetZ, offsetZ + endOffsetZ));

			if (current.distTo(end) <= HIT_DISTANCE) {
				SPELL_OWNER_UNIT_GET = owner;
				SPELL_TARGET_UNIT_GET = target;
				SPELL_IS_FOCUSED_GET = isFocused;
				SPELL_ACTIONS_ID_GET = actionsId;
				SPELL_OBJECT_GET = object;
				SPELL_X_GET = end.x;
				SPELL_Y_GET = end.y;
				if (actionsId != 0) {
					ExecuteFunc("Trig_" + TRIG_EVENT_SPELLS + "_Actions");
				} else {
					ExecuteFunc(func);
				}
				SPELL_OWNER_UNIT_GET = null;
				SPELL_TARGET_UNIT_GET = null;
				SPELL_IS_FOCUSED_GET = false;
				SPELL_ACTIONS_ID_GET = 0;
				SPELL_OBJECT_GET = 0;
				SPELL_X_GET = 0;
				SPELL_Y_GET = 0;
		
				this.destroy();
			}
			
		}
	}
		
	public function CreateStaticProjectile(
		unit owner,
		unit target,
		unit projectile,
		integer actionsId,
		real speed,
		real maxHeight,
		real offsetZ,
		boolean canFocusLost
	) -> StaticProjectile {
		return StaticProjectile.create(
			0,
			owner,
			target,
			projectile,
			actionsId,
			"",
			speed,
			maxHeight,
			offsetZ,
			canFocusLost
		);
	}

	public function CreateStaticProjectileFunc(
		integer object,
		unit owner,
		unit target,
		unit projectile,
		string func,
		real speed,
		real maxHeight,
		real offsetZ,
		boolean canFocusLost
	) -> StaticProjectile {
		return StaticProjectile.create(
			object,
			owner,
			target,
			projectile,
			0,
			func,
			speed,
			maxHeight,
			offsetZ,
			canFocusLost
		);
	}

	
	public function CreateStaticProjectileAtFunc(
		integer object,
		unit owner,
		real x,
		real y,
		unit projectile,
		string func,
		real speed,
		real maxHeight,
		real offsetZ
	) -> StaticProjectile {
		return StaticProjectile.createAt(
			object,
			owner,
			x,
			y,
			projectile,
			0,
			func,
			speed,
			maxHeight,
			offsetZ
		);
	}

}

// 2022
Replies (2)
20
library Parabolas requires Points {

	public struct Parabola {
		Point2D start;
		Point2D end;
		real apsis;

		static method create(Point2D start, Point2D end, real apsis) -> Parabola {
			return Parabola.allocate().setAt(start, end, apsis);
		}

		method destroy() {
			this.start.destroy();
			this.end.destroy();
			this.deallocate();
		}

		method setAt(Point2D start, Point2D end, real apsis) -> Parabola {
			this.start = start;
			this.end = end;
			this.apsis = apsis;
			return this;
		}

		method setStart(Point2D start) -> Parabola {
			this.start.copy(start);
			return this;
		}

		method setEnd(Point2D end) -> Parabola {
			this.end.copy(end);
			return this;
		}

		method setapsis(real apsis) -> Parabola {
			this.apsis = apsis;
			return this;
		}
	
		method calculateZ(Point2D current, real startOffsetZ, real endOffsetZ) -> real {
			real startHeight = start.getTerrainZ() + startOffsetZ;
			real endHeight = end.getTerrainZ() + endOffsetZ;
			real totalDistance = start.distTo(end);
			real currentDistance = start.distTo(current);
			real apsis = MaxReal(startHeight, endHeight) + apsis;
			return ParabolaZ2(startHeight, endHeight, apsis, totalDistance, currentDistance);
		}

		method calculateZMaxHeight(Point2D current, real startOffsetZ, real endOffsetZ, real maxHeight) -> real {
			real startHeight = start.getTerrainZ() + startOffsetZ;
			real endHeight = MinReal(end.getTerrainZ() + endOffsetZ, start.getTerrainZ() + startOffsetZ + maxHeight);
			real totalDistance = start.distTo(end);
			real currentDistance = start.distTo(current);
			real maxApsis = MaxReal(startHeight, endHeight) + apsis;
			return ParabolaZ2(startHeight, endHeight, maxApsis, totalDistance, currentDistance);
		}
	}

}
20
library Points {

	public Point2D TempPoint;

	public struct Point2D {
		real x, y;
		
		// Constructors
		static method createAt(real x, real y) -> Point2D {
			return Point2D.allocate().setAt(x, y);
		}

		static method createAtWidget(widget which) -> Point2D {
			return Point2D.allocate().setAt(GetWidgetX(which), GetWidgetY(which));
		}
		
		static method create() -> Point2D {
			return Point2D.allocate().setAt(0.0, 0.0);
		}
		
		// Transitional behavior
		static method clone(Point2D another) -> Point2D {
			return Point2D.allocate().setAt(another.x, another.y);
		}
		
		method setAt(real x, real y) -> Point2D {
			this.x = x + GetRandomReal(-0.1, 0.1);
			this.y = y + GetRandomReal(-0.1, 0.1);
			return this;
		}

		method setAtWidget(widget which) -> Point2D {
			this.setAt(GetWidgetX(which), GetWidgetY(which));
			return this;
		}
	
		method clear() -> Point2D {
			return this.setAt(0.0, 0.0);
		}

		method copy(Point2D another) -> Point2D {
			return this.setAt(another.x, another.y);
		}

		// Hash
		method save(handle whichHandle, integer childKey) -> Point2D {
			SaveInteger(HT, GetHandleId(whichHandle), childKey, this);
			return this;
		}

		static method load(handle whichHandle, integer childKey) -> Point2D {
			return LoadInteger(HT, GetHandleId(whichHandle), childKey);
		}

		// Math
		method distTo(Point2D another) -> real {
			return DistanceBetweenCoords(this.x, this.y, another.x, another.y);
		}

		method distToCoords(real x, real y) -> real {
			return DistanceBetweenCoords(this.x, this.y, x, y);
		}

		method distToWidget(widget which) -> real {
			return DistanceBetweenCoords(this.x, this.y, GetWidgetX(which), GetWidgetY(which));
		}

		method angleTo(Point2D another) -> real {
			return AngleNormalize(AngleBetweenCoords(this.x, this.y, another.x, another.y));
		}

		method angleToCoords(real x, real y) -> real {
			return AngleNormalize(AngleBetweenCoords(this.x, this.y, x, y));
		}

		method angleFromCoords(real x, real y) -> real {
			return AngleNormalize(AngleBetweenCoords(x, y, this.x, this.y));
		}

		method movePolar(real distance, real angle) -> Point2D {
			this.x = GetPolarOffsetX(this.x, distance, angle);
			this.y = GetPolarOffsetY(this.y, distance, angle);
			return this;
		}

		method moveTowards(Point2D another, real distance) -> Point2D {
			real angle = this.angleTo(another);
			this.x = GetPolarOffsetX(this.x, distance, angle);
			this.y = GetPolarOffsetY(this.y, distance, angle);
			return this;
		}

		method getTerrainZ() -> real {
			return GetTerrainZ(this.x, this.y);
		}

		method getTerrainCliffZ() -> real {
			return (GetTerrainCliffLevel(this.x, this.y) - 1) * 128.;
		}

		method approachClosestAvailablePoint(Point2D another) {
			real angle = this.angleTo(another);
			real height = another.getTerrainCliffZ();
			while (IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) || AbsReal(this.getTerrainCliffZ() - height) > 100.) {
				this.movePolar(50, angle);
			}
		}
	}

}
23
Красиво, а с практической точки зрения, 3-4 игрока выполняющие каст одновременно, не заминусуют Fps в игре? 😅
Replies (1)
15
RvzerBro, ну из-за эффекта только.
10
фпс дроп как от ульта шейкера в 2008
чего не хватает, чтобы избежать его?
Replies (6)
23
Slonick, у шейкера разве было так много эффектов, его фиссура кажется проходила куда быстрее, чем эти огненные столбы.
Что ешо за фпс дроп?
+ Вопрос, про одновременные касты, причем тут шейкер...
26
RvzerBro, речь про эхо-слэм - ульт Шейкера, на основе веера ножей который, там много шок-вейвов разлетается.
23
Extremator, опять же, ответ не в те ворота, причём тут шейкер и способ его реализации, он кастует Один, а я спросил про несколько кастеров 😡
16
Slonick, скорее всего такую нагрузку дает создание такого кол-ва эффектов с этой моделькой огня.
23
Extremator, а лол я понил, он не ответил мне) а написал про тоже, шо и я 😅 Пардоньте
26
RvzerBro, он спросил про просадку FPS как при ульте Шейкера - там создаётся много дамми и они спавнят много моделек шок-вейва. Это обычно поднагружало рендер. На видео спамятся модельки оставляющие что-то типа уберсплатов, и их довольно много, поэтому при мульти-юзе будет тонна этих эффектов. Это всё же проблема, но не большая, т.к. точно такой же скилл раньше юзали называя его "катон" во всех "наруто-картах", и даже в больших масштабах.
RvzerBro, ты же упомянул фиссуру Шейкера, которая спавнила деструктейблы (разрушаемые объекты) в виде груд камней, которые временно существовали и мешали пройти. Да, в момент их спавна проигрывается спецэффект вулкана, который раскидывает искры, но это совсем не то, видимо.
Одновременные касты скиллов со спамом таких эффектов явно не являются пиком среди красивых и правильных реализаций, но - почему нет?))
15
if Tick == ( Tick / Fireball_EffectLimit ) * Fireball_EffectLimit then
=
if Tick == Tick then
ограничение создания эффектов не работает отсюда и проседы фпс
Replies (5)
15
LastUchiha, если и работает то крайне неинтуитивно (мне сонному в 4 часа ночи непонятно :) ), стоит объяснить в комментах либо переписать понятно ИМХО.
Типа, вот Fireball_EffectLimit = 3, и чё это 3 значит? Мне надо больше или меньше ставить чтобы не лагало?
А так спелл норм, но этот момент непонятен вообще.
30
ограничение создания эффектов не работает
У тебя с математекой всё в порядке? Сколько будет в целых числах 10 / 3 * 3?
15
nazarpunk, ладно, да, проглядел что константа интовая. Непривычный для меня способ такие вещи делать. Пойду спать...
30
Мне надо больше или меньше ставить чтобы не лагало?
Это значит что эффект будет спавниться каждое третье срабатывание таймера. Поставь больше, спавна станет меньше.
8
Неплохо возьму на заметку.
15
Отлично подойдет для ульты какому-нибудь магу! Единственное что, не хватает горения жертв после "прокатки" по ним огненного шара.
Replies (4)
15
Meddin, ну если ульту делать, мне кажется неплохо было бы в конце взрыв накинуть, ну и да, поджигать тоже.
26
Meddin, временный бафф с периодическим уроном - это по сути как отдельный скилл. Просто делаешь его и добавляешь активацию в момент нанесения урона юнитам - они дамажатся и поджигаются.
26
LastUchiha, оно же и так "взрывается" по мере своего движения.))
15
Extremator, ну ты всё равно понял что я имел ввиду)
Можно же ещё красотуль натаскать...
2
没有想到,在国外还有这么有趣的论坛,在讨论这一个我童年回忆的游戏。
The comment is deleted
To leave a comment please sign in to the site.